浏览代码

Merge remote-tracking branch 'origin/master' into set_sane_defaults_for_firebase

vishnukvmd 3 年之前
父节点
当前提交
b3d745ff1a
共有 49 个文件被更改,包括 502 次插入312 次删除
  1. 21 3
      README.md
  2. 13 0
      android/app/build.gradle
  3. 4 0
      android/app/src/fdroid/AndroidManifest.xml
  4. 37 0
      android/permissions.md
  5. 1 0
      fastlane/metadata/android/en-US/changelogs/293.txt
  6. 1 0
      fastlane/metadata/android/en-US/changelogs/317.txt
  7. 1 0
      fastlane/metadata/android/en-US/changelogs/330.txt
  8. 5 0
      fastlane/metadata/android/en-US/changelogs/331.txt
  9. 5 0
      fastlane/metadata/android/en-US/changelogs/333.txt
  10. 28 6
      fastlane/metadata/android/en-US/full_description.txt
  11. 二进制
      fastlane/metadata/android/en-US/images/icon.png
  12. 二进制
      fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
  13. 二进制
      fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
  14. 二进制
      fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
  15. 二进制
      fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
  16. 二进制
      fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
  17. 二进制
      fastlane/metadata/android/en-US/images/phoneScreenshots/6.png
  18. 二进制
      fastlane/metadata/android/en-US/images/phoneScreenshots/7.png
  19. 2 218
      lib/app.dart
  20. 1 0
      lib/core/constants.dart
  21. 1 1
      lib/core/error-reporting/super_logging.dart
  22. 5 2
      lib/db/files_db.dart
  23. 226 0
      lib/ente_theme_data.dart
  24. 4 1
      lib/main.dart
  25. 8 16
      lib/models/filters/important_items_filter.dart
  26. 7 1
      lib/services/local_sync_service.dart
  27. 5 1
      lib/services/memories_service.dart
  28. 7 1
      lib/services/remote_sync_service.dart
  29. 13 3
      lib/services/user_service.dart
  30. 1 1
      lib/ui/email_entry_page.dart
  31. 1 1
      lib/ui/gallery.dart
  32. 5 2
      lib/ui/home_widget.dart
  33. 1 1
      lib/ui/login_page.dart
  34. 2 2
      lib/ui/memories_widget.dart
  35. 1 1
      lib/ui/ott_verification_page.dart
  36. 2 2
      lib/ui/settings/account_section_widget.dart
  37. 5 5
      lib/ui/settings/backup_section_widget.dart
  38. 1 1
      lib/ui/settings/common_settings.dart
  39. 1 1
      lib/ui/settings/danger_section_widget.dart
  40. 6 6
      lib/ui/settings/info_section_widget.dart
  41. 3 3
      lib/ui/settings/security_section_widget.dart
  42. 3 3
      lib/ui/settings/social_section_widget.dart
  43. 2 2
      lib/ui/settings/support_section_widget.dart
  44. 36 14
      lib/ui/settings/theme_switch_widget.dart
  45. 18 8
      lib/ui/settings_page.dart
  46. 6 3
      lib/utils/email_util.dart
  47. 5 3
      lib/utils/file_uploader.dart
  48. 7 0
      pubspec.lock
  49. 1 0
      pubspec.yaml

+ 21 - 3
README.md

@@ -5,23 +5,31 @@
 We have open-source apps across Android, iOS, web and desktop that automatically backup your photos and videos.
 We have open-source apps across Android, iOS, web and desktop that automatically backup your photos and videos.
 
 
 This repository contains the code for our mobile apps, built with a lot of ❤️, and a little bit of [Flutter.](https://flutter.dev)
 This repository contains the code for our mobile apps, built with a lot of ❤️, and a little bit of [Flutter.](https://flutter.dev)
-![App Screenshots](https://user-images.githubusercontent.com/1161789/154794909-c391f947-266f-4298-956b-a67b5eb9a169.png)
+
+![App Screenshots](https://user-images.githubusercontent.com/24503581/175218240-fe5a0703-82c1-4750-bfea-abfd9f409a97.png)
+
+<br/>
 
 
 ## ✨ Features
 ## ✨ Features
 
 
 - Client side encryption (only you can view your photos and videos)
 - Client side encryption (only you can view your photos and videos)
 - Background sync
 - Background sync
+- Family plans
 - Shareable links for albums
 - Shareable links for albums
 - Highlights of memories from previous years
 - Highlights of memories from previous years
 - Ability to detect and delete duplicate files
 - Ability to detect and delete duplicate files
+- Light and dark mode
 - Image editor
 - Image editor
 - EXIF viewer
 - EXIF viewer
 - Ability to free up disk space by deleting backed up photos
 - Ability to free up disk space by deleting backed up photos
+- Support for Live Photos
 - Recycle bin
 - Recycle bin
 - 2FA
 - 2FA
 - Lockscreen
 - Lockscreen
 - Zero third-party tracking / analytics
 - Zero third-party tracking / analytics
 
 
+<br/>
+
 ## 📲 Installation
 ## 📲 Installation
 
 
 ### Android
 ### Android
@@ -37,12 +45,14 @@ You can alternatively install the build from PlayStore or F-Droid.
   <img width="197" alt="Get it on F-Droid" src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png">
   <img width="197" alt="Get it on F-Droid" src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png">
 </a>
 </a>
 
 
-
 ### iOS
 ### iOS
+
 <a href="https://apps.apple.com/in/app/ente-photos/id1542026904">
 <a href="https://apps.apple.com/in/app/ente-photos/id1542026904">
   <img width="197" alt="Download on AppStore" src="https://user-images.githubusercontent.com/1161789/154795157-c4468ff9-97fd-46f3-87fe-dca789d8733a.png">
   <img width="197" alt="Download on AppStore" src="https://user-images.githubusercontent.com/1161789/154795157-c4468ff9-97fd-46f3-87fe-dca789d8733a.png">
 </a>
 </a>
 
 
+<br/>
+<br/>
 
 
 ## 🧑‍💻 Building from source
 ## 🧑‍💻 Building from source
 
 
@@ -52,22 +62,30 @@ You can alternatively install the build from PlayStore or F-Droid.
 4. For Android, run `flutter build apk --release --flavor independent`
 4. For Android, run `flutter build apk --release --flavor independent`
 5. For iOS, run `flutter build ios` 
 5. For iOS, run `flutter build ios` 
 
 
+<br/>
+
 ## 🙋 Help
 ## 🙋 Help
 
 
 We provide human support to our customers. Please write to [support@ente.io](mailto:support@ente.io) sharing as many details as possible about whatever it is that you need help with, and we will get back to you as soon as possible.
 We provide human support to our customers. Please write to [support@ente.io](mailto:support@ente.io) sharing as many details as possible about whatever it is that you need help with, and we will get back to you as soon as possible.
 
 
+<br/>
+
 ## 🧭 Roadmap
 ## 🧭 Roadmap
 
 
 We maintain a public roadmap, that's driven by our community @ [roadmap.ente.io](https://roadmap.ente.io).
 We maintain a public roadmap, that's driven by our community @ [roadmap.ente.io](https://roadmap.ente.io).
 
 
+<br/>
+
 ## 🤗 Support
 ## 🤗 Support
 
 
 If you like this project, please consider upgrading to a paid subscription.
 If you like this project, please consider upgrading to a paid subscription.
 
 
 If you would like to motivate us to keep building, you can do so by [starring](https://github.com/ente-io/frame/stargazers) this project.
 If you would like to motivate us to keep building, you can do so by [starring](https://github.com/ente-io/frame/stargazers) this project.
 
 
+<br/>
+
 ## ❤️ Join the Community
 ## ❤️ Join the Community
 
 
-Follow us on [Twitter](https://twitter.com/enteio) and join [r/enteio](https://reddit.com/r/enteio) to get regular updates, connect with other customers, and discuss your ideas.
+Follow us on [Twitter](https://twitter.com/enteio), join [r/enteio](https://reddit.com/r/enteio) or hang out on our [Discord](https://ente.io/discord) to get regular updates, connect with other customers, and discuss your ideas.
 
 
 An important part of our journey is to build better software by consistently listening to community feedback. Please feel free to [share your thoughts](mailto:feedback@ente.io) with us at any time.
 An important part of our journey is to build better software by consistently listening to community feedback. Please feel free to [share your thoughts](mailto:feedback@ente.io) with us at any time.

+ 13 - 0
android/app/build.gradle

@@ -72,6 +72,11 @@ android {
         playstore {
         playstore {
             dimension "default"
             dimension "default"
         }
         }
+        fdroid {
+            dimension "default"
+            applicationIdSuffix ".fdroid"
+            signingConfig null
+        }
     }
     }
 
 
     buildTypes {
     buildTypes {
@@ -88,6 +93,14 @@ android {
           }
           }
         }
         }
     }
     }
+
+    android.applicationVariants.all { variant ->
+        if (variant.flavorName == "fdroid") {
+            variant.outputs.all { output ->
+                output.outputFileName = "app-fdroid-release.apk"
+            }
+        }
+    }
 }
 }
 
 
 rootProject.allprojects {
 rootProject.allprojects {

+ 4 - 0
android/app/src/fdroid/AndroidManifest.xml

@@ -0,0 +1,4 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
+    package="io.ente.photos">
+    <uses-permission android:name="com.android.vending.BILLING"  tools:node="remove"/>
+</manifest>

+ 37 - 0
android/permissions.md

@@ -0,0 +1,37 @@
+## Android Permissions
+
+> android.permission.READ_EXTERNAL_STORAGE
+
+  **Used to read photos and videos from the device.**
+
+> android.permission.READ_CONTACTS
+
+**Used when a customer tries to pick a contact in the phonebook to share an album with. This is an optional permission, which users can deny if their android version allows granting granular permission.**
+
+> android.permission.ACCESS_MEDIA_LOCATION
+
+**Used to extract the coordinates a photo/video was captured in. This information is encrypted with the customer's key before being sent to our servers.**
+
+> android.permission.WRITE_EXTERNAL_STORAGE
+
+**Used for downloading photos to the disk.**
+
+> android.permission.USE_BIOMETRIC
+
+**Used to optionally lock the app behind the default lock screen.**
+
+> android.permission.USE_FINGERPRINT
+
+Used to optionally lock the app behind the default lock screen.
+
+> android.permission.RECEIVE_BOOT_COMPLETED
+
+**Used to trigger background uploads for photos and videos that were clicked.**
+
+> android.permission.USE_FULL_SCREEN_INTENT
+
+**This is needed by the local notification library, to show notifications about your previous memories.**
+
+> android.permission.SET_WALLPAPER
+
+**This allows the user to set a particular photo as wallpaper.**

+ 1 - 0
fastlane/metadata/android/en-US/changelogs/293.txt

@@ -0,0 +1 @@
+- Hello, FDroid!

+ 1 - 0
fastlane/metadata/android/en-US/changelogs/317.txt

@@ -0,0 +1 @@
+- Resync files with missing location data on Android 10+

+ 1 - 0
fastlane/metadata/android/en-US/changelogs/330.txt

@@ -0,0 +1 @@
+New pixels!

+ 5 - 0
fastlane/metadata/android/en-US/changelogs/331.txt

@@ -0,0 +1,5 @@
+Major release with fresh new pixels! ✨
+
+- Redesigned app look with support for light and dark modes
+- Performance improvements to speed up bulk file deletes
+- New swipe gestures to quickly switch top level tabs and navigate between screens

+ 5 - 0
fastlane/metadata/android/en-US/changelogs/333.txt

@@ -0,0 +1,5 @@
+Major release with fresh new pixels! ✨
+
+- Redesigned app look with support for light and dark modes
+- New swipe gestures to quickly switch between tabs and to navigate between screens
+- Performance improvements to speed up bulk file deletes

+ 28 - 6
fastlane/metadata/android/en-US/full_description.txt

@@ -1,11 +1,33 @@
-ente provides a simple way to back up your memories.
+Ente is a simple app to automatically backup and organize your photos and videos.
 
 
-ente encrypts your photos and videos with your password before backing them up to the cloud, so only you can view them.
+If you've been looking for a privacy-friendly alternative to preserve your memories, you've come to the right place. With Ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them.
 
 
-ente stores your encrypted data across multiple locations, including an underground fall out shelter, so your memories are preserved forever.
+We have apps across Android, iOS, web and Desktop, and your photos will seamlessly sync between all your devices in an end-to-end encrypted (e2ee) manner.
 
 
-ente lets you share your albums and folders with your loved ones, end-to-end encrypted.
+Ente also makes it simple to share your albums with your loved ones. You can either share them directly with other Ente users, end-to-end encrypted; or with publicly viewable links.
 
 
-ente has an open architecture and source code that has been peer-reviewed trusted.
+Your encrypted data is stored across multiple locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
 
 
-ente is available on android, ios and the web.
+We are here to make the safest photos app ever, come join our journey!
+
+FEATURES
+- Original quality backups, because every pixel is important
+- Family plans, so you can share storage with your family
+- Shared folders, in case you want your partner to enjoy your "Camera" clicks
+- Album links, that can be protected with a password and set to expire
+- Ability to free up space, by removing files that have been safely backed up
+- Image editor, to add finishing touches
+- Favorite, hide and relive your memories, for they are precious
+- One-click import from Google, Apple, your hard drive and more
+- Dark theme, because your photos look good in it
+- 2FA, 3FA, biometric auth
+- and a LOT more!
+
+PERMISSIONS
+Ente requests for certain permissions to serve the purpose of a photo storage provider, which can be reviewed here: https://github.com/ente-io/frame/blob/f-droid/android/permissions.md
+
+PRICING
+We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io.
+
+SUPPORT
+We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours.

二进制
fastlane/metadata/android/en-US/images/icon.png


二进制
fastlane/metadata/android/en-US/images/phoneScreenshots/1.png


二进制
fastlane/metadata/android/en-US/images/phoneScreenshots/2.png


二进制
fastlane/metadata/android/en-US/images/phoneScreenshots/3.png


二进制
fastlane/metadata/android/en-US/images/phoneScreenshots/4.png


二进制
fastlane/metadata/android/en-US/images/phoneScreenshots/5.png


二进制
fastlane/metadata/android/en-US/images/phoneScreenshots/6.png


二进制
fastlane/metadata/android/en-US/images/phoneScreenshots/7.png


+ 2 - 218
lib/app.dart

@@ -2,7 +2,6 @@ import 'dart:io';
 
 
 import 'package:adaptive_theme/adaptive_theme.dart';
 import 'package:adaptive_theme/adaptive_theme.dart';
 import 'package:background_fetch/background_fetch.dart';
 import 'package:background_fetch/background_fetch.dart';
-import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_easyloading/flutter_easyloading.dart';
 import 'package:flutter_easyloading/flutter_easyloading.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -15,221 +14,6 @@ import 'package:photos/services/app_lifecycle_service.dart';
 import 'package:photos/services/sync_service.dart';
 import 'package:photos/services/sync_service.dart';
 import 'package:photos/ui/home_widget.dart';
 import 'package:photos/ui/home_widget.dart';
 
 
-final lightThemeData = ThemeData(
-  fontFamily: 'Inter',
-  brightness: Brightness.light,
-  hintColor: Colors.grey,
-  primaryColor: Colors.deepOrangeAccent,
-  primaryColorLight: Colors.black54,
-  iconTheme: IconThemeData(color: Colors.black),
-  primaryIconTheme: IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
-  colorScheme: ColorScheme.light(
-    primary: Colors.black,
-    secondary: Color.fromARGB(255, 163, 163, 163),
-  ),
-  accentColor: Color.fromRGBO(0, 0, 0, 0.6),
-  buttonColor: Color.fromRGBO(45, 194, 98, 1.0),
-  outlinedButtonTheme: buildOutlinedButtonThemeData(
-    bgDisabled: Colors.grey.shade500,
-    bgEnabled: Colors.black,
-    fgDisabled: Colors.white,
-    fgEnabled: Colors.white,
-  ),
-  elevatedButtonTheme: buildElevatedButtonThemeData(
-    onPrimary: Colors.white,
-    primary: Colors.black,
-  ),
-  toggleableActiveColor: Colors.green[400],
-  scaffoldBackgroundColor: Colors.white,
-  backgroundColor: Colors.white,
-  appBarTheme: AppBarTheme().copyWith(
-    backgroundColor: Colors.white,
-    foregroundColor: Colors.black,
-    iconTheme: IconThemeData(color: Colors.black),
-    elevation: 0,
-  ),
-  //https://api.flutter.dev/flutter/material/TextTheme-class.html
-  textTheme: _buildTextTheme(Colors.black),
-  primaryTextTheme: TextTheme().copyWith(
-    bodyText2: TextStyle(color: Colors.yellow),
-    bodyText1: TextStyle(color: Colors.orange),
-  ),
-  cardColor: Color.fromRGBO(250, 250, 250, 1.0),
-  dialogTheme: DialogTheme().copyWith(
-    backgroundColor: Color.fromRGBO(250, 250, 250, 1.0), //
-    titleTextStyle: TextStyle(
-      color: Colors.black,
-      fontSize: 24,
-      fontWeight: FontWeight.w600,
-    ),
-    contentTextStyle: TextStyle(
-      fontFamily: 'Inter-Medium',
-      color: Colors.black,
-      fontSize: 16,
-      fontWeight: FontWeight.w500,
-    ),
-    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
-  ),
-  inputDecorationTheme: InputDecorationTheme().copyWith(
-    focusedBorder: UnderlineInputBorder(
-      borderSide: BorderSide(
-        color: Color.fromRGBO(45, 194, 98, 1.0),
-      ),
-    ),
-  ),
-  checkboxTheme: CheckboxThemeData(
-    side: BorderSide(
-      color: Colors.black,
-      width: 2,
-    ),
-    fillColor: MaterialStateProperty.resolveWith((states) {
-      return states.contains(MaterialState.selected)
-          ? Colors.black
-          : Colors.white;
-    }),
-    checkColor: MaterialStateProperty.resolveWith((states) {
-      return states.contains(MaterialState.selected)
-          ? Colors.white
-          : Colors.black;
-    }),
-  ),
-);
-
-final darkThemeData = ThemeData(
-  fontFamily: 'Inter',
-  brightness: Brightness.dark,
-  primaryColorLight: Colors.white70,
-  iconTheme: IconThemeData(color: Colors.white),
-  primaryIconTheme: IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
-  hintColor: Colors.grey,
-  colorScheme: ColorScheme.dark(primary: Colors.white),
-  accentColor: Color.fromRGBO(45, 194, 98, 0.2),
-  buttonColor: Color.fromRGBO(45, 194, 98, 1.0),
-  buttonTheme: ButtonThemeData().copyWith(
-    buttonColor: Color.fromRGBO(45, 194, 98, 1.0),
-  ),
-  textTheme: _buildTextTheme(Colors.white),
-  toggleableActiveColor: Colors.green[400],
-  outlinedButtonTheme: buildOutlinedButtonThemeData(
-    bgDisabled: Colors.grey.shade500,
-    bgEnabled: Colors.white,
-    fgDisabled: Colors.white,
-    fgEnabled: Colors.black,
-  ),
-  elevatedButtonTheme: buildElevatedButtonThemeData(
-    onPrimary: Colors.black,
-    primary: Colors.white,
-  ),
-  scaffoldBackgroundColor: Colors.black,
-  backgroundColor: Colors.black,
-  appBarTheme: AppBarTheme().copyWith(
-    color: Colors.black,
-    elevation: 0,
-  ),
-  cardColor: Color.fromRGBO(10, 15, 15, 1.0),
-  dialogTheme: DialogTheme().copyWith(
-    backgroundColor: Color.fromRGBO(15, 15, 15, 1.0),
-    titleTextStyle: TextStyle(
-      color: Colors.white,
-      fontSize: 24,
-      fontWeight: FontWeight.w600,
-    ),
-    contentTextStyle: TextStyle(
-      fontFamily: 'Inter-Medium',
-      color: Colors.white,
-      fontSize: 16,
-      fontWeight: FontWeight.w500,
-    ),
-    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
-  ),
-  inputDecorationTheme: InputDecorationTheme().copyWith(
-    focusedBorder: UnderlineInputBorder(
-      borderSide: BorderSide(
-        color: Color.fromRGBO(45, 194, 98, 1.0),
-      ),
-    ),
-  ),
-  checkboxTheme: CheckboxThemeData(
-    side: BorderSide(
-      color: Colors.grey,
-      width: 2,
-    ),
-    fillColor: MaterialStateProperty.resolveWith((states) {
-      if (states.contains(MaterialState.selected)) {
-        return Colors.grey;
-      } else {
-        return Colors.black;
-      }
-    }),
-    checkColor: MaterialStateProperty.resolveWith((states) {
-      if (states.contains(MaterialState.selected)) {
-        return Colors.black;
-      } else {
-        return Colors.grey;
-      }
-    }),
-  ),
-);
-
-TextTheme _buildTextTheme(Color textColor) {
-  return TextTheme().copyWith(
-    headline4: TextStyle(
-      color: textColor,
-      fontSize: 32,
-      fontWeight: FontWeight.w600,
-      fontFamily: 'Inter',
-    ),
-    headline5: TextStyle(
-      color: textColor,
-      fontSize: 24,
-      fontWeight: FontWeight.w600,
-      fontFamily: 'Inter',
-    ),
-    headline6: TextStyle(
-      color: textColor,
-      fontSize: 18,
-      fontFamily: 'Inter',
-      fontWeight: FontWeight.w600,
-    ),
-    subtitle1: TextStyle(
-      color: textColor,
-      fontFamily: 'Inter',
-      fontSize: 16,
-      fontWeight: FontWeight.w500,
-    ),
-    subtitle2: TextStyle(
-      color: textColor,
-      fontFamily: 'Inter',
-      fontSize: 14,
-      fontWeight: FontWeight.w500,
-    ),
-    bodyText1: TextStyle(
-      fontFamily: 'Inter',
-      color: textColor,
-      fontSize: 16,
-      fontWeight: FontWeight.w500,
-    ),
-    bodyText2: TextStyle(
-      fontFamily: 'Inter',
-      color: textColor,
-      fontSize: 14,
-      fontWeight: FontWeight.w500,
-    ),
-    caption: TextStyle(
-      color: textColor.withOpacity(0.6),
-      fontSize: 14,
-      fontWeight: FontWeight.w500,
-    ),
-    overline: TextStyle(
-      fontFamily: 'Inter',
-      color: textColor,
-      fontSize: 14,
-      fontWeight: FontWeight.w500,
-      decoration: TextDecoration.underline,
-    ),
-  );
-}
-
 class EnteApp extends StatefulWidget {
 class EnteApp extends StatefulWidget {
   static const _homeWidget = HomeWidget();
   static const _homeWidget = HomeWidget();
 
 
@@ -329,11 +113,11 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
           stopOnTerminate: false,
           stopOnTerminate: false,
           startOnBoot: true,
           startOnBoot: true,
           enableHeadless: true,
           enableHeadless: true,
-          requiresBatteryNotLow: false,
+          requiresBatteryNotLow: true,
           requiresCharging: false,
           requiresCharging: false,
           requiresStorageNotLow: false,
           requiresStorageNotLow: false,
           requiresDeviceIdle: false,
           requiresDeviceIdle: false,
-          requiredNetworkType: NetworkType.NONE,
+          requiredNetworkType: NetworkType.ANY,
         ), (String taskId) async {
         ), (String taskId) async {
       await widget.runBackgroundTask(taskId);
       await widget.runBackgroundTask(taskId);
     }, (taskId) {
     }, (taskId) {

+ 1 - 0
lib/core/constants.dart

@@ -12,6 +12,7 @@ const String kRoadmapURL = "https://roadmap.ente.io";
 const int kMicroSecondsInDay = 86400000000;
 const int kMicroSecondsInDay = 86400000000;
 const int kAndroid11SDKINT = 30;
 const int kAndroid11SDKINT = 30;
 const int kGalleryLoadStartTime = -8000000000000000; // Wednesday, March 6, 1748
 const int kGalleryLoadStartTime = -8000000000000000; // Wednesday, March 6, 1748
+const int kGalleryLoadEndTime = 9223372036854775807; // 2^63 -1
 
 
 // used to identify which ente file are available in app cache
 // used to identify which ente file are available in app cache
 const String kSharedMediaIdentifier = 'ente-shared://';
 const String kSharedMediaIdentifier = 'ente-shared://';

+ 1 - 1
lib/core/error-reporting/super_logging.dart

@@ -176,7 +176,7 @@ class SuperLogging {
 
 
     if (config.body == null) return;
     if (config.body == null) return;
 
 
-    if (enable) {
+    if (enable && sentryIsEnabled) {
       await SentryFlutter.init(
       await SentryFlutter.init(
         (options) {
         (options) {
           options.dsn = config.sentryDsn;
           options.dsn = config.sentryDsn;

+ 5 - 2
lib/db/files_db.dart

@@ -580,9 +580,10 @@ class FilesDB {
 
 
   Future<List<File>> getFilesCreatedWithinDurations(
   Future<List<File>> getFilesCreatedWithinDurations(
     List<List<int>> durations,
     List<List<int>> durations,
+    Set<int> ignoredCollectionIDs,
   ) async {
   ) async {
     final db = await instance.database;
     final db = await instance.database;
-    String whereClause = "";
+    String whereClause = "( ";
     for (int index = 0; index < durations.length; index++) {
     for (int index = 0; index < durations.length; index++) {
       whereClause += "($columnCreationTime > " +
       whereClause += "($columnCreationTime > " +
           durations[index][0].toString() +
           durations[index][0].toString() +
@@ -593,12 +594,14 @@ class FilesDB {
         whereClause += " OR ";
         whereClause += " OR ";
       }
       }
     }
     }
+    whereClause += ") AND $columnMMdVisibility = $kVisibilityVisible";
     final results = await db.query(
     final results = await db.query(
       table,
       table,
       where: whereClause,
       where: whereClause,
       orderBy: '$columnCreationTime ASC',
       orderBy: '$columnCreationTime ASC',
     );
     );
-    return _convertToFiles(results);
+    final files = _convertToFiles(results);
+    return _deduplicatedAndFilterIgnoredFiles(files, ignoredCollectionIDs);
   }
   }
 
 
   Future<List<File>> getFilesToBeUploadedWithinFolders(
   Future<List<File>> getFilesToBeUploadedWithinFolders(

+ 226 - 0
lib/ente_theme_data.dart

@@ -1,6 +1,221 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
 import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
 
 
+final lightThemeData = ThemeData(
+  fontFamily: 'Inter',
+  brightness: Brightness.light,
+  hintColor: Colors.grey,
+  primaryColor: Colors.deepOrangeAccent,
+  primaryColorLight: Colors.black54,
+  iconTheme: IconThemeData(color: Colors.black),
+  primaryIconTheme: IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
+  colorScheme: ColorScheme.light(
+    primary: Colors.black,
+    secondary: Color.fromARGB(255, 163, 163, 163),
+  ),
+  accentColor: Color.fromRGBO(0, 0, 0, 0.6),
+  buttonColor: Color.fromRGBO(45, 194, 98, 1.0),
+  outlinedButtonTheme: buildOutlinedButtonThemeData(
+    bgDisabled: Colors.grey.shade500,
+    bgEnabled: Colors.black,
+    fgDisabled: Colors.white,
+    fgEnabled: Colors.white,
+  ),
+  elevatedButtonTheme: buildElevatedButtonThemeData(
+    onPrimary: Colors.white,
+    primary: Colors.black,
+  ),
+  toggleableActiveColor: Colors.green[400],
+  scaffoldBackgroundColor: Colors.white,
+  backgroundColor: Colors.white,
+  appBarTheme: AppBarTheme().copyWith(
+    backgroundColor: Colors.white,
+    foregroundColor: Colors.black,
+    iconTheme: IconThemeData(color: Colors.black),
+    elevation: 0,
+  ),
+  //https://api.flutter.dev/flutter/material/TextTheme-class.html
+  textTheme: _buildTextTheme(Colors.black),
+  primaryTextTheme: TextTheme().copyWith(
+    bodyText2: TextStyle(color: Colors.yellow),
+    bodyText1: TextStyle(color: Colors.orange),
+  ),
+  cardColor: Color.fromRGBO(250, 250, 250, 1.0),
+  dialogTheme: DialogTheme().copyWith(
+    backgroundColor: Color.fromRGBO(250, 250, 250, 1.0), //
+    titleTextStyle: TextStyle(
+      color: Colors.black,
+      fontSize: 24,
+      fontWeight: FontWeight.w600,
+    ),
+    contentTextStyle: TextStyle(
+      fontFamily: 'Inter-Medium',
+      color: Colors.black,
+      fontSize: 16,
+      fontWeight: FontWeight.w500,
+    ),
+    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
+  ),
+  inputDecorationTheme: InputDecorationTheme().copyWith(
+    focusedBorder: UnderlineInputBorder(
+      borderSide: BorderSide(
+        color: Color.fromRGBO(45, 194, 98, 1.0),
+      ),
+    ),
+  ),
+  checkboxTheme: CheckboxThemeData(
+    side: BorderSide(
+      color: Colors.black,
+      width: 2,
+    ),
+    fillColor: MaterialStateProperty.resolveWith((states) {
+      return states.contains(MaterialState.selected)
+          ? Colors.black
+          : Colors.white;
+    }),
+    checkColor: MaterialStateProperty.resolveWith((states) {
+      return states.contains(MaterialState.selected)
+          ? Colors.white
+          : Colors.black;
+    }),
+  ),
+);
+
+final darkThemeData = ThemeData(
+  fontFamily: 'Inter',
+  brightness: Brightness.dark,
+  primaryColorLight: Colors.white70,
+  iconTheme: IconThemeData(color: Colors.white),
+  primaryIconTheme: IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
+  hintColor: Colors.grey,
+  colorScheme: ColorScheme.dark(primary: Colors.white),
+  accentColor: Color.fromRGBO(45, 194, 98, 0.2),
+  buttonColor: Color.fromRGBO(45, 194, 98, 1.0),
+  buttonTheme: ButtonThemeData().copyWith(
+    buttonColor: Color.fromRGBO(45, 194, 98, 1.0),
+  ),
+  textTheme: _buildTextTheme(Colors.white),
+  toggleableActiveColor: Colors.green[400],
+  outlinedButtonTheme: buildOutlinedButtonThemeData(
+    bgDisabled: Colors.grey.shade500,
+    bgEnabled: Colors.white,
+    fgDisabled: Colors.white,
+    fgEnabled: Colors.black,
+  ),
+  elevatedButtonTheme: buildElevatedButtonThemeData(
+    onPrimary: Colors.black,
+    primary: Colors.white,
+  ),
+  scaffoldBackgroundColor: Colors.black,
+  backgroundColor: Colors.black,
+  appBarTheme: AppBarTheme().copyWith(
+    color: Colors.black,
+    elevation: 0,
+  ),
+  cardColor: Color.fromRGBO(10, 15, 15, 1.0),
+  dialogTheme: DialogTheme().copyWith(
+    backgroundColor: Color.fromRGBO(15, 15, 15, 1.0),
+    titleTextStyle: TextStyle(
+      color: Colors.white,
+      fontSize: 24,
+      fontWeight: FontWeight.w600,
+    ),
+    contentTextStyle: TextStyle(
+      fontFamily: 'Inter-Medium',
+      color: Colors.white,
+      fontSize: 16,
+      fontWeight: FontWeight.w500,
+    ),
+    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
+  ),
+  inputDecorationTheme: InputDecorationTheme().copyWith(
+    focusedBorder: UnderlineInputBorder(
+      borderSide: BorderSide(
+        color: Color.fromRGBO(45, 194, 98, 1.0),
+      ),
+    ),
+  ),
+  checkboxTheme: CheckboxThemeData(
+    side: BorderSide(
+      color: Colors.grey,
+      width: 2,
+    ),
+    fillColor: MaterialStateProperty.resolveWith((states) {
+      if (states.contains(MaterialState.selected)) {
+        return Colors.grey;
+      } else {
+        return Colors.black;
+      }
+    }),
+    checkColor: MaterialStateProperty.resolveWith((states) {
+      if (states.contains(MaterialState.selected)) {
+        return Colors.black;
+      } else {
+        return Colors.grey;
+      }
+    }),
+  ),
+);
+
+TextTheme _buildTextTheme(Color textColor) {
+  return TextTheme().copyWith(
+    headline4: TextStyle(
+      color: textColor,
+      fontSize: 32,
+      fontWeight: FontWeight.w600,
+      fontFamily: 'Inter',
+    ),
+    headline5: TextStyle(
+      color: textColor,
+      fontSize: 24,
+      fontWeight: FontWeight.w600,
+      fontFamily: 'Inter',
+    ),
+    headline6: TextStyle(
+      color: textColor,
+      fontSize: 18,
+      fontFamily: 'Inter',
+      fontWeight: FontWeight.w600,
+    ),
+    subtitle1: TextStyle(
+      color: textColor,
+      fontFamily: 'Inter',
+      fontSize: 16,
+      fontWeight: FontWeight.w500,
+    ),
+    subtitle2: TextStyle(
+      color: textColor,
+      fontFamily: 'Inter',
+      fontSize: 14,
+      fontWeight: FontWeight.w500,
+    ),
+    bodyText1: TextStyle(
+      fontFamily: 'Inter',
+      color: textColor,
+      fontSize: 16,
+      fontWeight: FontWeight.w500,
+    ),
+    bodyText2: TextStyle(
+      fontFamily: 'Inter',
+      color: textColor,
+      fontSize: 14,
+      fontWeight: FontWeight.w500,
+    ),
+    caption: TextStyle(
+      color: textColor.withOpacity(0.6),
+      fontSize: 14,
+      fontWeight: FontWeight.w500,
+    ),
+    overline: TextStyle(
+      fontFamily: 'Inter',
+      color: textColor,
+      fontSize: 14,
+      fontWeight: FontWeight.w500,
+      decoration: TextDecoration.underline,
+    ),
+  );
+}
+
 extension CustomColorScheme on ColorScheme {
 extension CustomColorScheme on ColorScheme {
   Color get defaultBackgroundColor =>
   Color get defaultBackgroundColor =>
       brightness == Brightness.light ? Colors.white : Colors.black;
       brightness == Brightness.light ? Colors.white : Colors.black;
@@ -121,6 +336,17 @@ extension CustomColorScheme on ColorScheme {
   Color get subTextColor => brightness == Brightness.light
   Color get subTextColor => brightness == Brightness.light
       ? Color.fromRGBO(180, 180, 180, 1)
       ? Color.fromRGBO(180, 180, 180, 1)
       : Color.fromRGBO(100, 100, 100, 1);
       : Color.fromRGBO(100, 100, 100, 1);
+
+  Color get themeSwitchIndicatorColor => brightness == Brightness.light
+      ? Colors.black.withOpacity(0.75)
+      : Colors.white;
+
+  Color get themeSwitchActiveIconColor =>
+      brightness == Brightness.light ? Colors.white : Colors.black;
+
+  Color get themeSwitchInactiveIconColor => brightness == Brightness.light
+      ? Colors.black.withOpacity(0.5)
+      : Colors.white.withOpacity(0.5);
 }
 }
 
 
 OutlinedButtonThemeData buildOutlinedButtonThemeData({
 OutlinedButtonThemeData buildOutlinedButtonThemeData({

+ 4 - 1
lib/main.dart

@@ -15,6 +15,7 @@ import 'package:photos/core/constants.dart';
 import 'package:photos/core/error-reporting/super_logging.dart';
 import 'package:photos/core/error-reporting/super_logging.dart';
 import 'package:photos/core/network.dart';
 import 'package:photos/core/network.dart';
 import 'package:photos/db/upload_locks_db.dart';
 import 'package:photos/db/upload_locks_db.dart';
+import 'package:photos/ente_theme_data.dart';
 import 'package:photos/services/app_lifecycle_service.dart';
 import 'package:photos/services/app_lifecycle_service.dart';
 import 'package:photos/services/billing_service.dart';
 import 'package:photos/services/billing_service.dart';
 import 'package:photos/services/collections_service.dart';
 import 'package:photos/services/collections_service.dart';
@@ -28,6 +29,7 @@ import 'package:photos/services/remote_sync_service.dart';
 import 'package:photos/services/sync_service.dart';
 import 'package:photos/services/sync_service.dart';
 import 'package:photos/services/trash_sync_service.dart';
 import 'package:photos/services/trash_sync_service.dart';
 import 'package:photos/services/update_service.dart';
 import 'package:photos/services/update_service.dart';
+import 'package:photos/services/user_service.dart';
 import 'package:photos/ui/app_lock.dart';
 import 'package:photos/ui/app_lock.dart';
 import 'package:photos/ui/lock_screen.dart';
 import 'package:photos/ui/lock_screen.dart';
 import 'package:photos/utils/crypto_util.dart';
 import 'package:photos/utils/crypto_util.dart';
@@ -130,6 +132,7 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
   await NotificationService.instance.init();
   await NotificationService.instance.init();
   await Network.instance.init();
   await Network.instance.init();
   await Configuration.instance.init();
   await Configuration.instance.init();
+  await UserService.instance.init();
   await UpdateService.instance.init();
   await UpdateService.instance.init();
   await BillingService.instance.init();
   await BillingService.instance.init();
   await CollectionsService.instance.init();
   await CollectionsService.instance.init();
@@ -169,7 +172,7 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
   await SuperLogging.main(
   await SuperLogging.main(
     LogConfig(
     LogConfig(
       body: function,
       body: function,
-      logDirPath: (await getTemporaryDirectory()).path + "/logs",
+      logDirPath: (await getApplicationSupportDirectory()).path + "/logs",
       maxLogFiles: 5,
       maxLogFiles: 5,
       sentryDsn: kDebugMode ? kSentryDebugDSN : kSentryDSN,
       sentryDsn: kDebugMode ? kSentryDebugDSN : kSentryDSN,
       tunnel: kSentryTunnel,
       tunnel: kSentryTunnel,

+ 8 - 16
lib/models/filters/important_items_filter.dart

@@ -10,25 +10,17 @@ class ImportantItemsFilter implements GalleryItemsFilter {
 
 
   @override
   @override
   bool shouldInclude(File file) {
   bool shouldInclude(File file) {
-    if (_importantPaths.isEmpty) {
-      if (Platform.isAndroid) {
-        if (file.uploadedFileID != null) {
-          return true;
-        }
-        final String folder = basename(file.deviceFolder);
-        return folder == "Camera" ||
-            folder == "Recents" ||
-            folder == "DCIM" ||
-            folder == "Download" ||
-            folder == "Screenshot";
-      } else {
-        return true;
-      }
-    }
     if (file.uploadedFileID != null) {
     if (file.uploadedFileID != null) {
       return true;
       return true;
     }
     }
-    final folder = basename(file.deviceFolder);
+    final String folder = basename(file.deviceFolder);
+    if (_importantPaths.isEmpty && Platform.isAndroid) {
+      return folder == "Camera" ||
+          folder == "Recents" ||
+          folder == "DCIM" ||
+          folder == "Download" ||
+          folder == "Screenshot";
+    }
     return _importantPaths.contains(folder);
     return _importantPaths.contains(folder);
   }
   }
 }
 }

+ 7 - 1
lib/services/local_sync_service.dart

@@ -275,12 +275,18 @@ class LocalSyncService {
   }
   }
 
 
   void _registerChangeCallback() {
   void _registerChangeCallback() {
+    // In case of iOS limit permission, this call back is fired immediately
+    // after file selection dialog is dismissed.
     PhotoManager.addChangeCallback((value) async {
     PhotoManager.addChangeCallback((value) async {
       _logger.info("Something changed on disk");
       _logger.info("Something changed on disk");
       if (_existingSync != null) {
       if (_existingSync != null) {
         await _existingSync.future;
         await _existingSync.future;
       }
       }
-      sync();
+      if (hasGrantedLimitedPermissions()) {
+        syncAll();
+      } else {
+        sync();
+      }
     });
     });
     PhotoManager.startChangeNotify();
     PhotoManager.startChangeNotify();
   }
   }

+ 5 - 1
lib/services/memories_service.dart

@@ -5,6 +5,7 @@ import 'package:photos/db/files_db.dart';
 import 'package:photos/db/memories_db.dart';
 import 'package:photos/db/memories_db.dart';
 import 'package:photos/models/filters/important_items_filter.dart';
 import 'package:photos/models/filters/important_items_filter.dart';
 import 'package:photos/models/memory.dart';
 import 'package:photos/models/memory.dart';
+import 'package:photos/services/collections_service.dart';
 
 
 class MemoriesService extends ChangeNotifier {
 class MemoriesService extends ChangeNotifier {
   final _logger = Logger("MemoryService");
   final _logger = Logger("MemoryService");
@@ -70,7 +71,10 @@ class MemoriesService extends ChangeNotifier {
           date.add(Duration(days: daysAfter)).microsecondsSinceEpoch;
           date.add(Duration(days: daysAfter)).microsecondsSinceEpoch;
       durations.add([startCreationTime, endCreationTime]);
       durations.add([startCreationTime, endCreationTime]);
     }
     }
-    final files = await _filesDB.getFilesCreatedWithinDurations(durations);
+    final archivedCollectionIds =
+        CollectionsService.instance.getArchivedCollections();
+    final files = await _filesDB.getFilesCreatedWithinDurations(
+        durations, archivedCollectionIds);
     final seenTimes = await _memoriesDB.getSeenTimes();
     final seenTimes = await _memoriesDB.getSeenTimes();
     final List<Memory> memories = [];
     final List<Memory> memories = [];
     final filter = ImportantItemsFilter();
     final filter = ImportantItemsFilter();

+ 7 - 1
lib/services/remote_sync_service.dart

@@ -105,7 +105,6 @@ class RemoteSyncService {
         _existingSync = null;
         _existingSync = null;
       }
       }
     } catch (e, s) {
     } catch (e, s) {
-      _logger.severe("Error executing remote sync ", 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.
@@ -114,7 +113,10 @@ class RemoteSyncService {
           e is WiFiUnavailableError ||
           e is WiFiUnavailableError ||
           e is StorageLimitExceededError ||
           e is StorageLimitExceededError ||
           e is SyncStopRequestedError) {
           e is SyncStopRequestedError) {
+        _logger.warning("Error executing remote sync", e);
         rethrow;
         rethrow;
+      } else {
+        _logger.severe("Error executing remote sync ", e, s);
       }
       }
     }
     }
   }
   }
@@ -268,6 +270,10 @@ class RemoteSyncService {
 
 
     if (toBeUploaded > 0) {
     if (toBeUploaded > 0) {
       Bus.instance.fire(SyncStatusUpdate(SyncStatus.preparing_for_upload));
       Bus.instance.fire(SyncStatusUpdate(SyncStatus.preparing_for_upload));
+      // verify if files upload is allowed based on their subscription plan and
+      // storage limit. To avoid creating new endpoint, we are using
+      // fetchUploadUrls as alternative method.
+      await _uploader.fetchUploadURLs(toBeUploaded);
     }
     }
     final List<Future> futures = [];
     final List<Future> futures = [];
     for (final uploadedFileID in updatedFileIDs) {
     for (final uploadedFileID in updatedFileIDs) {

+ 13 - 3
lib/services/user_service.dart

@@ -33,16 +33,21 @@ class UserService {
   final _dio = Network.instance.getDio();
   final _dio = Network.instance.getDio();
   final _logger = Logger((UserService).toString());
   final _logger = Logger((UserService).toString());
   final _config = Configuration.instance;
   final _config = Configuration.instance;
+  ValueNotifier<String> emailValueNotifier;
 
 
   UserService._privateConstructor();
   UserService._privateConstructor();
-
   static final UserService instance = UserService._privateConstructor();
   static final UserService instance = UserService._privateConstructor();
 
 
+  Future<void> init() async {
+    emailValueNotifier =
+        ValueNotifier<String>(Configuration.instance.getEmail());
+  }
+
   Future<void> getOtt(
   Future<void> getOtt(
     BuildContext context,
     BuildContext context,
     String email, {
     String email, {
     bool isChangeEmail = false,
     bool isChangeEmail = false,
-    bool isCreateAccountScreen,
+    bool isCreateAccountScreen = false,
   }) async {
   }) async {
     final dialog = createProgressDialog(context, "Please wait...");
     final dialog = createProgressDialog(context, "Please wait...");
     await dialog.show();
     await dialog.show();
@@ -266,6 +271,11 @@ class UserService {
     }
     }
   }
   }
 
 
+  Future<void> setEmail(String email) async {
+    await _config.setEmail(email);
+    emailValueNotifier.value = email ?? "";
+  }
+
   Future<void> changeEmail(
   Future<void> changeEmail(
     BuildContext context,
     BuildContext context,
     String email,
     String email,
@@ -289,7 +299,7 @@ class UserService {
       await dialog.hide();
       await dialog.hide();
       if (response != null && response.statusCode == 200) {
       if (response != null && response.statusCode == 200) {
         showToast(context, "Email changed to " + email);
         showToast(context, "Email changed to " + email);
-        _config.setEmail(email);
+        await setEmail(email);
         Navigator.of(context).popUntil((route) => route.isFirst);
         Navigator.of(context).popUntil((route) => route.isFirst);
         Bus.instance.fire(UserDetailsChangedEvent());
         Bus.instance.fire(UserDetailsChangedEvent());
         return;
         return;

+ 1 - 1
lib/ui/email_entry_page.dart

@@ -108,7 +108,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
         buttonText: 'Create account',
         buttonText: 'Create account',
         onPressedFunction: () {
         onPressedFunction: () {
           _config.setVolatilePassword(_passwordController1.text);
           _config.setVolatilePassword(_passwordController1.text);
-          _config.setEmail(_email);
+          UserService.instance.setEmail(_email);
           UserService.instance
           UserService.instance
               .getOtt(context, _email, isCreateAccountScreen: true);
               .getOtt(context, _email, isCreateAccountScreen: true);
           FocusScope.of(context).unfocus();
           FocusScope.of(context).unfocus();

+ 1 - 1
lib/ui/gallery.dart

@@ -114,7 +114,7 @@ class _GalleryState extends State<Gallery> {
       final startTime = DateTime.now().microsecondsSinceEpoch;
       final startTime = DateTime.now().microsecondsSinceEpoch;
       final result = await widget.asyncLoader(
       final result = await widget.asyncLoader(
         kGalleryLoadStartTime,
         kGalleryLoadStartTime,
-        DateTime.now().microsecondsSinceEpoch,
+        kGalleryLoadEndTime,
         limit: limit,
         limit: limit,
       );
       );
       final endTime = DateTime.now().microsecondsSinceEpoch;
       final endTime = DateTime.now().microsecondsSinceEpoch;

+ 5 - 2
lib/ui/home_widget.dart

@@ -21,6 +21,7 @@ import 'package:photos/events/subscription_purchased_event.dart';
 import 'package:photos/events/sync_status_update_event.dart';
 import 'package:photos/events/sync_status_update_event.dart';
 import 'package:photos/events/tab_changed_event.dart';
 import 'package:photos/events/tab_changed_event.dart';
 import 'package:photos/events/trigger_logout_event.dart';
 import 'package:photos/events/trigger_logout_event.dart';
+import 'package:photos/events/user_details_changed_event.dart';
 import 'package:photos/events/user_logged_out_event.dart';
 import 'package:photos/events/user_logged_out_event.dart';
 import 'package:photos/models/file_load_result.dart';
 import 'package:photos/models/file_load_result.dart';
 import 'package:photos/models/galleryType.dart';
 import 'package:photos/models/galleryType.dart';
@@ -64,7 +65,9 @@ class HomeWidget extends StatefulWidget {
 class _HomeWidgetState extends State<HomeWidget> {
 class _HomeWidgetState extends State<HomeWidget> {
   static const _deviceFolderGalleryWidget = CollectionsGalleryWidget();
   static const _deviceFolderGalleryWidget = CollectionsGalleryWidget();
   static const _sharedCollectionGallery = SharedCollectionGallery();
   static const _sharedCollectionGallery = SharedCollectionGallery();
-  static const _settingsPage = SettingsPage();
+  static final _settingsPage = SettingsPage(
+    emailNotifier: UserService.instance.emailValueNotifier,
+  );
   static const _headerWidget = HeaderWidget();
   static const _headerWidget = HeaderWidget();
 
 
   final _logger = Logger("HomeWidgetState");
   final _logger = Logger("HomeWidgetState");
@@ -566,7 +569,7 @@ class _HomeBottomNavigationBarState extends State<HomeBottomNavigationBar> {
     _tabChangedEventSubscription =
     _tabChangedEventSubscription =
         Bus.instance.on<TabChangedEvent>().listen((event) {
         Bus.instance.on<TabChangedEvent>().listen((event) {
       if (event.source != TabChangedEventSource.tab_bar) {
       if (event.source != TabChangedEventSource.tab_bar) {
-        _logger.fine('index changed to ${event.selectedIndex}');
+        debugPrint('index changed to ${event.selectedIndex}');
         if (mounted) {
         if (mounted) {
           setState(() {
           setState(() {
             currentTabIndex = event.selectedIndex;
             currentTabIndex = event.selectedIndex;

+ 1 - 1
lib/ui/login_page.dart

@@ -55,7 +55,7 @@ class _LoginPageState extends State<LoginPage> {
         isFormValid: _emailIsValid,
         isFormValid: _emailIsValid,
         buttonText: 'Log in',
         buttonText: 'Log in',
         onPressedFunction: () {
         onPressedFunction: () {
-          _config.setEmail(_email);
+          UserService.instance.setEmail(_email);
           UserService.instance
           UserService.instance
               .getOtt(context, _email, isCreateAccountScreen: false);
               .getOtt(context, _email, isCreateAccountScreen: false);
           FocusScope.of(context).unfocus();
           FocusScope.of(context).unfocus();

+ 2 - 2
lib/ui/memories_widget.dart

@@ -334,8 +334,8 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
       child: IconButton(
       child: IconButton(
         icon: Icon(
         icon: Icon(
           Icons.adaptive.share,
           Icons.adaptive.share,
-          color: Colors.white,
-        ), //same for both themes
+          color: Colors.white, //same for both themes
+        ),
         onPressed: () {
         onPressed: () {
           share(context, [file]);
           share(context, [file]);
         },
         },

+ 1 - 1
lib/ui/ott_verification_page.dart

@@ -12,7 +12,7 @@ class OTTVerificationPage extends StatefulWidget {
   OTTVerificationPage(
   OTTVerificationPage(
     this.email, {
     this.email, {
     this.isChangeEmail = false,
     this.isChangeEmail = false,
-    this.isCreateAccountScreen,
+    this.isCreateAccountScreen = false,
     Key key,
     Key key,
   }) : super(key: key);
   }) : super(key: key);
 
 

+ 2 - 2
lib/ui/settings/account_section_widget.dart

@@ -70,7 +70,7 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
           child:
           child:
               SettingsTextItem(text: "Recovery key", icon: Icons.navigate_next),
               SettingsTextItem(text: "Recovery key", icon: Icons.navigate_next),
         ),
         ),
-        SectionOptionDivider,
+        sectionOptionDivider,
         GestureDetector(
         GestureDetector(
           behavior: HitTestBehavior.translucent,
           behavior: HitTestBehavior.translucent,
           onTap: () async {
           onTap: () async {
@@ -95,7 +95,7 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
           child:
           child:
               SettingsTextItem(text: "Change email", icon: Icons.navigate_next),
               SettingsTextItem(text: "Change email", icon: Icons.navigate_next),
         ),
         ),
-        SectionOptionDivider,
+        sectionOptionDivider,
         GestureDetector(
         GestureDetector(
           behavior: HitTestBehavior.translucent,
           behavior: HitTestBehavior.translucent,
           onTap: () async {
           onTap: () async {

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

@@ -55,7 +55,7 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
           icon: Icons.navigate_next,
           icon: Icons.navigate_next,
         ),
         ),
       ),
       ),
-      SectionOptionDivider,
+      sectionOptionDivider,
       SizedBox(
       SizedBox(
         height: 48,
         height: 48,
         child: Row(
         child: Row(
@@ -75,7 +75,7 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
           ],
           ],
         ),
         ),
       ),
       ),
-      SectionOptionDivider,
+      sectionOptionDivider,
       SizedBox(
       SizedBox(
         height: 48,
         height: 48,
         child: Row(
         child: Row(
@@ -98,7 +98,7 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
     ];
     ];
     if (Platform.isIOS) {
     if (Platform.isIOS) {
       sectionOptions.addAll([
       sectionOptions.addAll([
-        SectionOptionDivider,
+        sectionOptionDivider,
         SizedBox(
         SizedBox(
           height: 48,
           height: 48,
           child: Row(
           child: Row(
@@ -134,7 +134,7 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
     }
     }
     sectionOptions.addAll(
     sectionOptions.addAll(
       [
       [
-        SectionOptionDivider,
+        sectionOptionDivider,
         GestureDetector(
         GestureDetector(
           behavior: HitTestBehavior.translucent,
           behavior: HitTestBehavior.translucent,
           onTap: () async {
           onTap: () async {
@@ -168,7 +168,7 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
             icon: Icons.navigate_next,
             icon: Icons.navigate_next,
           ),
           ),
         ),
         ),
-        SectionOptionDivider,
+        sectionOptionDivider,
         GestureDetector(
         GestureDetector(
           behavior: HitTestBehavior.translucent,
           behavior: HitTestBehavior.translucent,
           onTap: () async {
           onTap: () async {

+ 1 - 1
lib/ui/settings/common_settings.dart

@@ -4,7 +4,7 @@ import 'package:expandable/expandable.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
-Widget SectionOptionDivider = Padding(
+Widget sectionOptionDivider = Padding(
   padding: EdgeInsets.all(Platform.isIOS ? 4 : 2),
   padding: EdgeInsets.all(Platform.isIOS ? 4 : 2),
 );
 );
 
 

+ 1 - 1
lib/ui/settings/danger_section_widget.dart

@@ -35,7 +35,7 @@ class _DangerSectionWidgetState extends State<DangerSectionWidget> {
           },
           },
           child: SettingsTextItem(text: "Logout", icon: Icons.navigate_next),
           child: SettingsTextItem(text: "Logout", icon: Icons.navigate_next),
         ),
         ),
-        SectionOptionDivider,
+        sectionOptionDivider,
         GestureDetector(
         GestureDetector(
           behavior: HitTestBehavior.translucent,
           behavior: HitTestBehavior.translucent,
           onTap: () {
           onTap: () {

+ 6 - 6
lib/ui/settings/info_section_widget.dart

@@ -39,7 +39,7 @@ class InfoSectionWidget extends StatelessWidget {
           },
           },
           child: SettingsTextItem(text: "FAQ", icon: Icons.navigate_next),
           child: SettingsTextItem(text: "FAQ", icon: Icons.navigate_next),
         ),
         ),
-        SectionOptionDivider,
+        sectionOptionDivider,
         GestureDetector(
         GestureDetector(
           behavior: HitTestBehavior.translucent,
           behavior: HitTestBehavior.translucent,
           onTap: () {
           onTap: () {
@@ -53,7 +53,7 @@ class InfoSectionWidget extends StatelessWidget {
           },
           },
           child: SettingsTextItem(text: "Terms", icon: Icons.navigate_next),
           child: SettingsTextItem(text: "Terms", icon: Icons.navigate_next),
         ),
         ),
-        SectionOptionDivider,
+        sectionOptionDivider,
         GestureDetector(
         GestureDetector(
           behavior: HitTestBehavior.translucent,
           behavior: HitTestBehavior.translucent,
           onTap: () {
           onTap: () {
@@ -67,19 +67,19 @@ class InfoSectionWidget extends StatelessWidget {
           },
           },
           child: SettingsTextItem(text: "Privacy", icon: Icons.navigate_next),
           child: SettingsTextItem(text: "Privacy", icon: Icons.navigate_next),
         ),
         ),
-        SectionOptionDivider,
+        sectionOptionDivider,
         GestureDetector(
         GestureDetector(
           behavior: HitTestBehavior.translucent,
           behavior: HitTestBehavior.translucent,
           onTap: () async {
           onTap: () async {
-            launch("https://github.com/ente-io/frame");
+            launchUrl(Uri.parse("https://github.com/ente-io/frame"));
           },
           },
           child:
           child:
               SettingsTextItem(text: "Source code", icon: Icons.navigate_next),
               SettingsTextItem(text: "Source code", icon: Icons.navigate_next),
         ),
         ),
+        sectionOptionDivider,
         UpdateService.instance.isIndependent()
         UpdateService.instance.isIndependent()
             ? Column(
             ? Column(
                 children: [
                 children: [
-                  Divider(height: 4),
                   GestureDetector(
                   GestureDetector(
                     behavior: HitTestBehavior.translucent,
                     behavior: HitTestBehavior.translucent,
                     onTap: () async {
                     onTap: () async {
@@ -110,7 +110,7 @@ class InfoSectionWidget extends StatelessWidget {
                   ),
                   ),
                 ],
                 ],
               )
               )
-            : Container(),
+            : const SizedBox.shrink(),
       ],
       ],
     );
     );
   }
   }

+ 3 - 3
lib/ui/settings/security_section_widget.dart

@@ -116,7 +116,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
       );
       );
     }
     }
     children.addAll([
     children.addAll([
-      SectionOptionDivider,
+      sectionOptionDivider,
       SizedBox(
       SizedBox(
         height: 48,
         height: 48,
         child: Row(
         child: Row(
@@ -150,7 +150,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
     if (Platform.isAndroid) {
     if (Platform.isAndroid) {
       children.addAll(
       children.addAll(
         [
         [
-          SectionOptionDivider,
+          sectionOptionDivider,
           SizedBox(
           SizedBox(
             height: 48,
             height: 48,
             child: Row(
             child: Row(
@@ -246,7 +246,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
       );
       );
     }
     }
     children.addAll([
     children.addAll([
-      SectionOptionDivider,
+      sectionOptionDivider,
       GestureDetector(
       GestureDetector(
         behavior: HitTestBehavior.translucent,
         behavior: HitTestBehavior.translucent,
         onTap: () async {
         onTap: () async {

+ 3 - 3
lib/ui/settings/social_section_widget.dart

@@ -30,7 +30,7 @@ class SocialSectionWidget extends StatelessWidget {
         },
         },
         child: SettingsTextItem(text: "Twitter", icon: Icons.navigate_next),
         child: SettingsTextItem(text: "Twitter", icon: Icons.navigate_next),
       ),
       ),
-      SectionOptionDivider,
+      sectionOptionDivider,
       GestureDetector(
       GestureDetector(
         behavior: HitTestBehavior.translucent,
         behavior: HitTestBehavior.translucent,
         onTap: () {
         onTap: () {
@@ -38,7 +38,7 @@ class SocialSectionWidget extends StatelessWidget {
         },
         },
         child: SettingsTextItem(text: "Discord", icon: Icons.navigate_next),
         child: SettingsTextItem(text: "Discord", icon: Icons.navigate_next),
       ),
       ),
-      SectionOptionDivider,
+      sectionOptionDivider,
       GestureDetector(
       GestureDetector(
         behavior: HitTestBehavior.translucent,
         behavior: HitTestBehavior.translucent,
         onTap: () {
         onTap: () {
@@ -50,7 +50,7 @@ class SocialSectionWidget extends StatelessWidget {
     if (!UpdateService.instance.isIndependent()) {
     if (!UpdateService.instance.isIndependent()) {
       options.addAll(
       options.addAll(
         [
         [
-          SectionOptionDivider,
+          sectionOptionDivider,
           GestureDetector(
           GestureDetector(
             behavior: HitTestBehavior.translucent,
             behavior: HitTestBehavior.translucent,
             onTap: () {
             onTap: () {

+ 2 - 2
lib/ui/settings/support_section_widget.dart

@@ -43,7 +43,7 @@ class SupportSectionWidget extends StatelessWidget {
           },
           },
           child: SettingsTextItem(text: "Email", icon: Icons.navigate_next),
           child: SettingsTextItem(text: "Email", icon: Icons.navigate_next),
         ),
         ),
-        SectionOptionDivider,
+        sectionOptionDivider,
         GestureDetector(
         GestureDetector(
           behavior: HitTestBehavior.translucent,
           behavior: HitTestBehavior.translucent,
           onTap: () {
           onTap: () {
@@ -63,7 +63,7 @@ class SupportSectionWidget extends StatelessWidget {
           },
           },
           child: SettingsTextItem(text: "Roadmap", icon: Icons.navigate_next),
           child: SettingsTextItem(text: "Roadmap", icon: Icons.navigate_next),
         ),
         ),
-        SectionOptionDivider,
+        sectionOptionDivider,
         GestureDetector(
         GestureDetector(
           behavior: HitTestBehavior.translucent,
           behavior: HitTestBehavior.translucent,
           onTap: () async {
           onTap: () async {

+ 36 - 14
lib/ui/settings/theme_switch_widget.dart

@@ -1,30 +1,52 @@
 import 'package:adaptive_theme/adaptive_theme.dart';
 import 'package:adaptive_theme/adaptive_theme.dart';
+import 'package:animated_toggle_switch/animated_toggle_switch.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
+import 'package:photos/ente_theme_data.dart';
 
 
 class ThemeSwitchWidget extends StatelessWidget {
 class ThemeSwitchWidget extends StatelessWidget {
   const ThemeSwitchWidget({Key key}) : super(key: key);
   const ThemeSwitchWidget({Key key}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return Switch.adaptive(
-      value: Theme.of(context).colorScheme.onSurface == Colors.black,
-      activeColor: Theme.of(context).colorScheme.onSurface,
-      activeTrackColor: Theme.of(context).colorScheme.onBackground,
-      inactiveTrackColor: Theme.of(context).colorScheme.onSurface,
-      inactiveThumbColor: Theme.of(context).colorScheme.primary,
-      hoverColor: Theme.of(context).colorScheme.onSurface,
-      onChanged: (bool value) {
-        if (value) {
+    var selectedTheme = 0;
+    if (Theme.of(context).brightness == Brightness.dark) {
+      selectedTheme = 1;
+    }
+    return AnimatedToggleSwitch<int>.rolling(
+      current: selectedTheme,
+      values: const [0, 1],
+      onChanged: (i) {
+        print("Changed to ${i}, selectedTheme is ${selectedTheme} ");
+        if (i == 0) {
           AdaptiveTheme.of(context).setLight();
           AdaptiveTheme.of(context).setLight();
         } else {
         } else {
           AdaptiveTheme.of(context).setDark();
           AdaptiveTheme.of(context).setDark();
         }
         }
       },
       },
-      // activeThumbImage: new NetworkImage(
-      //     'https://cdn0.iconfinder.com/data/icons/multimedia-solid-30px/30/moon_dark_mode_night-512.png'),
-      // inactiveThumbImage: new NetworkImage(
-      //     'https://cdn0.iconfinder.com/data/icons/multimedia-solid-30px/30/moon_dark_mode_night-512.png'),
+      iconBuilder: (i, size, foreground) {
+        final color = selectedTheme == i
+            ? Theme.of(context).colorScheme.themeSwitchActiveIconColor
+            : Theme.of(context).colorScheme.themeSwitchInactiveIconColor;
+        if (i == 0) {
+          return Icon(
+            Icons.light_mode,
+            color: color,
+          );
+        } else {
+          return Icon(
+            Icons.dark_mode,
+            color: color,
+          );
+        }
+      },
+      height: 36,
+      indicatorSize: Size(36, 36),
+      indicatorColor: Theme.of(context).colorScheme.themeSwitchIndicatorColor,
+      borderColor: Theme.of(context)
+          .colorScheme
+          .themeSwitchInactiveIconColor
+          .withOpacity(0.1),
+      borderWidth: 1,
     );
     );
   }
   }
 }
 }

+ 18 - 8
lib/ui/settings_page.dart

@@ -16,7 +16,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 {
-  const SettingsPage({Key key}) : 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) {
@@ -27,7 +28,6 @@ class SettingsPage extends StatelessWidget {
 
 
   Widget _getBody(BuildContext context) {
   Widget _getBody(BuildContext context) {
     final hasLoggedIn = Configuration.instance.getToken() != null;
     final hasLoggedIn = Configuration.instance.getToken() != null;
-    final String email = Configuration.instance.getEmail();
     final List<Widget> contents = [];
     final List<Widget> contents = [];
     contents.add(
     contents.add(
       Container(
       Container(
@@ -36,13 +36,23 @@ class SettingsPage extends StatelessWidget {
           mainAxisAlignment: MainAxisAlignment.spaceBetween,
           mainAxisAlignment: MainAxisAlignment.spaceBetween,
           crossAxisAlignment: CrossAxisAlignment.center,
           crossAxisAlignment: CrossAxisAlignment.center,
           children: [
           children: [
-            Text(
-              email,
-              style: Theme.of(context)
-                  .textTheme
-                  .subtitle1
-                  .copyWith(overflow: TextOverflow.ellipsis),
+            // // Thanks to the [AnimatedBuilder], only the widget displaying the
+            // // current email is rebuilt when `emailNotifier` notifies its
+            // // listeners.
+            AnimatedBuilder(
+              // [AnimatedBuilder] accepts any [Listenable] subtype.
+              animation: emailNotifier,
+              builder: (BuildContext context, Widget child) {
+                return Text(
+                  emailNotifier.value,
+                  style: Theme.of(context)
+                      .textTheme
+                      .subtitle1
+                      .copyWith(overflow: TextOverflow.ellipsis),
+                );
+              },
             ),
             ),
+
             (Platform.isAndroid)
             (Platform.isAndroid)
                 ? ThemeSwitchWidget()
                 ? ThemeSwitchWidget()
                 : const SizedBox.shrink(),
                 : const SizedBox.shrink(),

+ 6 - 3
lib/utils/email_util.dart

@@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
 import 'package:flutter_email_sender/flutter_email_sender.dart';
 import 'package:flutter_email_sender/flutter_email_sender.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:path_provider/path_provider.dart';
+import 'package:photos/core/configuration.dart';
 import 'package:photos/core/error-reporting/super_logging.dart';
 import 'package:photos/core/error-reporting/super_logging.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ui/common/dialogs.dart';
 import 'package:photos/ui/common/dialogs.dart';
@@ -80,7 +81,7 @@ Future<void> sendLogs(
   content.addAll(
   content.addAll(
     [
     [
       Text(
       Text(
-        "This will send across logs and metrics that will help us debug your issue better",
+        "This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.",
         style: TextStyle(
         style: TextStyle(
           height: 1.5,
           height: 1.5,
           fontSize: 16,
           fontSize: 16,
@@ -139,9 +140,11 @@ Future<void> _sendLogs(
 Future<String> getZippedLogsFile(BuildContext context) async {
 Future<String> getZippedLogsFile(BuildContext context) async {
   final dialog = createProgressDialog(context, "Preparing logs...");
   final dialog = createProgressDialog(context, "Preparing logs...");
   await dialog.show();
   await dialog.show();
+  final logsPath = (await getApplicationSupportDirectory()).path;
+  final logsDirectory = Directory(logsPath + "/logs");
   final tempPath = (await getTemporaryDirectory()).path;
   final tempPath = (await getTemporaryDirectory()).path;
-  final zipFilePath = tempPath + "/logs.zip";
-  final logsDirectory = Directory(tempPath + "/logs");
+  final zipFilePath =
+      tempPath + "/logs-${Configuration.instance.getUserID() ?? 0}.zip";
   var encoder = ZipFileEncoder();
   var encoder = ZipFileEncoder();
   encoder.create(zipFilePath);
   encoder.create(zipFilePath);
   encoder.addDirectory(logsDirectory);
   encoder.addDirectory(logsDirectory);

+ 5 - 3
lib/utils/file_uploader.dart

@@ -467,6 +467,8 @@ class FileUploader {
     } catch (e, s) {
     } catch (e, s) {
       if (!(e is NoActiveSubscriptionError ||
       if (!(e is NoActiveSubscriptionError ||
           e is StorageLimitExceededError ||
           e is StorageLimitExceededError ||
+          e is WiFiUnavailableError ||
+          e is SilentlyCancelUploadsError ||
           e is FileTooLargeForPlanError)) {
           e is FileTooLargeForPlanError)) {
         _logger.severe("File upload failed for " + file.toString(), e, s);
         _logger.severe("File upload failed for " + file.toString(), e, s);
       }
       }
@@ -650,20 +652,20 @@ class FileUploader {
 
 
   Future<UploadURL> _getUploadURL() async {
   Future<UploadURL> _getUploadURL() async {
     if (_uploadURLs.isEmpty) {
     if (_uploadURLs.isEmpty) {
-      await _fetchUploadURLs();
+      await fetchUploadURLs(_queue.length);
     }
     }
     return _uploadURLs.removeFirst();
     return _uploadURLs.removeFirst();
   }
   }
 
 
   Future<void> _uploadURLFetchInProgress;
   Future<void> _uploadURLFetchInProgress;
 
 
-  Future<void> _fetchUploadURLs() async {
+  Future<void> fetchUploadURLs(int fileCount) async {
     _uploadURLFetchInProgress ??= Future<void>(() async {
     _uploadURLFetchInProgress ??= Future<void>(() async {
       try {
       try {
         final response = await _dio.get(
         final response = await _dio.get(
           Configuration.instance.getHttpEndpoint() + "/files/upload-urls",
           Configuration.instance.getHttpEndpoint() + "/files/upload-urls",
           queryParameters: {
           queryParameters: {
-            "count": min(42, 2 * _queue.length), // m4gic number
+            "count": min(42, fileCount * 2), // m4gic number
           },
           },
           options: Options(
           options: Options(
             headers: {"X-Auth-Token": Configuration.instance.getToken()},
             headers: {"X-Auth-Token": Configuration.instance.getToken()},

+ 7 - 0
pubspec.lock

@@ -24,6 +24,13 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "2.1.0"
     version: "2.1.0"
+  animated_toggle_switch:
+    dependency: "direct main"
+    description:
+      name: animated_toggle_switch
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.5.2"
   archive:
   archive:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:

+ 1 - 0
pubspec.yaml

@@ -21,6 +21,7 @@ dependencies:
   alice:
   alice:
     git: "https://github.com/jhomlala/alice.git"
     git: "https://github.com/jhomlala/alice.git"
   animate_do: ^2.0.0
   animate_do: ^2.0.0
+  animated_toggle_switch: ^0.5.2
   archive: ^3.1.2
   archive: ^3.1.2
   background_fetch: ^1.0.1
   background_fetch: ^1.0.1
   bip39: ^1.0.6
   bip39: ^1.0.6