diff --git a/README.md b/README.md
index 98717f78324f60a3e4c2ddc92f68137c7b3b8005..b3890d916f1e3d20ea7d4f2b1341da168d22efff 100644
--- a/README.md
+++ b/README.md
@@ -5,23 +5,31 @@
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)
-
+
+
+
+
## ✨ Features
- Client side encryption (only you can view your photos and videos)
- Background sync
+- Family plans
- Shareable links for albums
- Highlights of memories from previous years
- Ability to detect and delete duplicate files
+- Light and dark mode
- Image editor
- EXIF viewer
- Ability to free up disk space by deleting backed up photos
+- Support for Live Photos
- Recycle bin
- 2FA
- Lockscreen
- Zero third-party tracking / analytics
+
+
## 📲 Installation
### Android
@@ -37,12 +45,14 @@ You can alternatively install the build from PlayStore or F-Droid.
-
### iOS
+
+
+
## 🧑💻 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`
5. For iOS, run `flutter build ios`
+
+
## 🙋 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.
+
+
## 🧭 Roadmap
We maintain a public roadmap, that's driven by our community @ [roadmap.ente.io](https://roadmap.ente.io).
+
+
## 🤗 Support
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.
+
+
## ❤️ 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.
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 9a9942287442540ea5d399b99459bbe8fbb20c0b..48c96b9f06832ec64e3ac32d55dd9e781d4d6d4e 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -72,6 +72,11 @@ android {
playstore {
dimension "default"
}
+ fdroid {
+ dimension "default"
+ applicationIdSuffix ".fdroid"
+ signingConfig null
+ }
}
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 {
diff --git a/android/app/src/fdroid/AndroidManifest.xml b/android/app/src/fdroid/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2d96997533114ab17dd8e272f03d147946e9be9b
--- /dev/null
+++ b/android/app/src/fdroid/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/android/permissions.md b/android/permissions.md
new file mode 100644
index 0000000000000000000000000000000000000000..4d3e1f424963b99dac45ae53d0b8ae00fa01dacc
--- /dev/null
+++ b/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.**
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/293.txt b/fastlane/metadata/android/en-US/changelogs/293.txt
new file mode 100644
index 0000000000000000000000000000000000000000..bbdcbdc37f546026cdd181426aecf07ecc36d368
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/293.txt
@@ -0,0 +1 @@
+- Hello, FDroid!
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/317.txt b/fastlane/metadata/android/en-US/changelogs/317.txt
new file mode 100644
index 0000000000000000000000000000000000000000..db137699a735baaa8789c212f57ef15d6b22f205
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/317.txt
@@ -0,0 +1 @@
+- Resync files with missing location data on Android 10+
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/330.txt b/fastlane/metadata/android/en-US/changelogs/330.txt
new file mode 100644
index 0000000000000000000000000000000000000000..91dd765cd3b68086186a776ee57688ac119c0ae8
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/330.txt
@@ -0,0 +1 @@
+New pixels!
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/331.txt b/fastlane/metadata/android/en-US/changelogs/331.txt
new file mode 100644
index 0000000000000000000000000000000000000000..16f5ad5650f598388e900748bae54785ea244cb0
--- /dev/null
+++ b/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
diff --git a/fastlane/metadata/android/en-US/changelogs/333.txt b/fastlane/metadata/android/en-US/changelogs/333.txt
new file mode 100644
index 0000000000000000000000000000000000000000..50c9efaa209b8650570f88b984d7a8b3797c5429
--- /dev/null
+++ b/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
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
index 4de7387ca3a191cf5e5af9f5a347e8de5d184ab2..71a1335304c6c993d3c033a49d5816992ead8124 100644
--- a/fastlane/metadata/android/en-US/full_description.txt
+++ b/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.
diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png
index dd45f4cd557b20db3dc018c88a13acfc258293a6..8b32155bb64666e14d9ffd6c510e2c3875541794 100644
Binary files a/fastlane/metadata/android/en-US/images/icon.png and b/fastlane/metadata/android/en-US/images/icon.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
index 2d78548ef1f7c729a5fbdc8a8f08373df6961b3c..7cab0bfe4fffa9560131e60a503e628ec9bef136 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
index 464ee15d793094ebf95c8f06896749bba9b344cc..15e8754656f41b915bcec17c9fdf4b2389ba92a5 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
index 77f216a3164c7a11063599eacdd0f082245bd0e8..91fb9da73ed836af257e4154ff330896bc0b4239 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
index 0414576977196c9a7b436eccba8709009b92dc85..cd47572c95e5d33249b555051be5b812707987e5 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
index 84eb8117a0f56e824cd66f6a395ef12dede2bbcf..4e5ca0bade2d2b5ffebaf5a6288142f3e7af14e8 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png
new file mode 100644
index 0000000000000000000000000000000000000000..39cfa966d4452a192ea723faff369da2ef4fc5f0
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png
new file mode 100644
index 0000000000000000000000000000000000000000..0b44226574d64e9ff4f63c4e330f222549c8bf6f
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png differ
diff --git a/lib/app.dart b/lib/app.dart
index a2eebbc0590b6dd3c5f4a690c8d4db44fae6051d..f12db02d06101dbff6b86534b3209cc41db92bd1 100644
--- a/lib/app.dart
+++ b/lib/app.dart
@@ -2,7 +2,6 @@ import 'dart:io';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:background_fetch/background_fetch.dart';
-import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.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/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 {
static const _homeWidget = HomeWidget();
@@ -329,11 +113,11 @@ class _EnteAppState extends State with WidgetsBindingObserver {
stopOnTerminate: false,
startOnBoot: true,
enableHeadless: true,
- requiresBatteryNotLow: false,
+ requiresBatteryNotLow: true,
requiresCharging: false,
requiresStorageNotLow: false,
requiresDeviceIdle: false,
- requiredNetworkType: NetworkType.NONE,
+ requiredNetworkType: NetworkType.ANY,
), (String taskId) async {
await widget.runBackgroundTask(taskId);
}, (taskId) {
diff --git a/lib/core/constants.dart b/lib/core/constants.dart
index d644060f787493dcca5ea40a080e186becfc8952..359fc317e5f93928768538eb1a635a6d68e221be 100644
--- a/lib/core/constants.dart
+++ b/lib/core/constants.dart
@@ -12,6 +12,7 @@ const String kRoadmapURL = "https://roadmap.ente.io";
const int kMicroSecondsInDay = 86400000000;
const int kAndroid11SDKINT = 30;
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
const String kSharedMediaIdentifier = 'ente-shared://';
diff --git a/lib/core/error-reporting/super_logging.dart b/lib/core/error-reporting/super_logging.dart
index 94bd638a9eb312d31418bf9900baad4f45b3df27..fe5d29ff5422479d58a7ad234cbff82b63bb87ee 100644
--- a/lib/core/error-reporting/super_logging.dart
+++ b/lib/core/error-reporting/super_logging.dart
@@ -176,7 +176,7 @@ class SuperLogging {
if (config.body == null) return;
- if (enable) {
+ if (enable && sentryIsEnabled) {
await SentryFlutter.init(
(options) {
options.dsn = config.sentryDsn;
diff --git a/lib/db/files_db.dart b/lib/db/files_db.dart
index 174ab52678aafdecca7e3b36492147714d24e6df..52e8d311a6bd852cfcbdbcbc615605c9bec6c51a 100644
--- a/lib/db/files_db.dart
+++ b/lib/db/files_db.dart
@@ -580,9 +580,10 @@ class FilesDB {
Future> getFilesCreatedWithinDurations(
List> durations,
+ Set ignoredCollectionIDs,
) async {
final db = await instance.database;
- String whereClause = "";
+ String whereClause = "( ";
for (int index = 0; index < durations.length; index++) {
whereClause += "($columnCreationTime > " +
durations[index][0].toString() +
@@ -593,12 +594,14 @@ class FilesDB {
whereClause += " OR ";
}
}
+ whereClause += ") AND $columnMMdVisibility = $kVisibilityVisible";
final results = await db.query(
table,
where: whereClause,
orderBy: '$columnCreationTime ASC',
);
- return _convertToFiles(results);
+ final files = _convertToFiles(results);
+ return _deduplicatedAndFilterIgnoredFiles(files, ignoredCollectionIDs);
}
Future> getFilesToBeUploadedWithinFolders(
diff --git a/lib/ente_theme_data.dart b/lib/ente_theme_data.dart
index 56ac134e8769bcd27e89d7538b1bb3f8cf6c5084..f84b8ce631da6b4d9332e1fc2464027180821054 100644
--- a/lib/ente_theme_data.dart
+++ b/lib/ente_theme_data.dart
@@ -1,6 +1,221 @@
import 'package:flutter/material.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 {
Color get defaultBackgroundColor =>
brightness == Brightness.light ? Colors.white : Colors.black;
@@ -121,6 +336,17 @@ extension CustomColorScheme on ColorScheme {
Color get subTextColor => brightness == Brightness.light
? Color.fromRGBO(180, 180, 180, 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({
diff --git a/lib/main.dart b/lib/main.dart
index 1690c246105b7677bd69934307675b432785cc85..d4544eba794dc523f0219bcb95e7b06dc88e04f4 100644
--- a/lib/main.dart
+++ b/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/network.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/billing_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/trash_sync_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/lock_screen.dart';
import 'package:photos/utils/crypto_util.dart';
@@ -130,6 +132,7 @@ Future _init(bool isBackground, {String via = ''}) async {
await NotificationService.instance.init();
await Network.instance.init();
await Configuration.instance.init();
+ await UserService.instance.init();
await UpdateService.instance.init();
await BillingService.instance.init();
await CollectionsService.instance.init();
@@ -169,7 +172,7 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
await SuperLogging.main(
LogConfig(
body: function,
- logDirPath: (await getTemporaryDirectory()).path + "/logs",
+ logDirPath: (await getApplicationSupportDirectory()).path + "/logs",
maxLogFiles: 5,
sentryDsn: kDebugMode ? kSentryDebugDSN : kSentryDSN,
tunnel: kSentryTunnel,
diff --git a/lib/models/filters/important_items_filter.dart b/lib/models/filters/important_items_filter.dart
index 4404f092810278044572423afd77f2d7b8cd8512..ffd3b4dc43d7cd86df52e7c1fad3577f791fb3d9 100644
--- a/lib/models/filters/important_items_filter.dart
+++ b/lib/models/filters/important_items_filter.dart
@@ -10,25 +10,17 @@ class ImportantItemsFilter implements GalleryItemsFilter {
@override
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) {
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);
}
}
diff --git a/lib/services/local_sync_service.dart b/lib/services/local_sync_service.dart
index 9c017dceb0787f9e1b5aeb08a160daae415c33cf..63fa9e670c5eb54121e47258c6162289aa20a2b8 100644
--- a/lib/services/local_sync_service.dart
+++ b/lib/services/local_sync_service.dart
@@ -275,12 +275,18 @@ class LocalSyncService {
}
void _registerChangeCallback() {
+ // In case of iOS limit permission, this call back is fired immediately
+ // after file selection dialog is dismissed.
PhotoManager.addChangeCallback((value) async {
_logger.info("Something changed on disk");
if (_existingSync != null) {
await _existingSync.future;
}
- sync();
+ if (hasGrantedLimitedPermissions()) {
+ syncAll();
+ } else {
+ sync();
+ }
});
PhotoManager.startChangeNotify();
}
diff --git a/lib/services/memories_service.dart b/lib/services/memories_service.dart
index 0179029bbfb0001d2c4de919d001a31ff11ab3ee..047f55249b0d92021408fe1a4df387cb3db5d47a 100644
--- a/lib/services/memories_service.dart
+++ b/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/models/filters/important_items_filter.dart';
import 'package:photos/models/memory.dart';
+import 'package:photos/services/collections_service.dart';
class MemoriesService extends ChangeNotifier {
final _logger = Logger("MemoryService");
@@ -70,7 +71,10 @@ class MemoriesService extends ChangeNotifier {
date.add(Duration(days: daysAfter)).microsecondsSinceEpoch;
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 List memories = [];
final filter = ImportantItemsFilter();
diff --git a/lib/services/remote_sync_service.dart b/lib/services/remote_sync_service.dart
index 4a7509be68eec9ceb2ac68d3c664d027266b8652..c25aa4b41f710fed3cf9464a999324e7c2a9d628 100644
--- a/lib/services/remote_sync_service.dart
+++ b/lib/services/remote_sync_service.dart
@@ -105,7 +105,6 @@ class RemoteSyncService {
_existingSync = null;
}
} catch (e, s) {
- _logger.severe("Error executing remote sync ", e, s);
_existingSync.complete();
_existingSync = null;
// rethrow whitelisted error so that UI status can be updated correctly.
@@ -114,7 +113,10 @@ class RemoteSyncService {
e is WiFiUnavailableError ||
e is StorageLimitExceededError ||
e is SyncStopRequestedError) {
+ _logger.warning("Error executing remote sync", e);
rethrow;
+ } else {
+ _logger.severe("Error executing remote sync ", e, s);
}
}
}
@@ -268,6 +270,10 @@ class RemoteSyncService {
if (toBeUploaded > 0) {
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 futures = [];
for (final uploadedFileID in updatedFileIDs) {
diff --git a/lib/services/user_service.dart b/lib/services/user_service.dart
index 47b44c6ec99fdaff2fcfc8e1af6fda6af62eccb4..65c4f8d48fc8f6f82207022a437f86a6624ace87 100644
--- a/lib/services/user_service.dart
+++ b/lib/services/user_service.dart
@@ -33,16 +33,21 @@ class UserService {
final _dio = Network.instance.getDio();
final _logger = Logger((UserService).toString());
final _config = Configuration.instance;
+ ValueNotifier emailValueNotifier;
UserService._privateConstructor();
-
static final UserService instance = UserService._privateConstructor();
+ Future init() async {
+ emailValueNotifier =
+ ValueNotifier(Configuration.instance.getEmail());
+ }
+
Future getOtt(
BuildContext context,
String email, {
bool isChangeEmail = false,
- bool isCreateAccountScreen,
+ bool isCreateAccountScreen = false,
}) async {
final dialog = createProgressDialog(context, "Please wait...");
await dialog.show();
@@ -266,6 +271,11 @@ class UserService {
}
}
+ Future setEmail(String email) async {
+ await _config.setEmail(email);
+ emailValueNotifier.value = email ?? "";
+ }
+
Future changeEmail(
BuildContext context,
String email,
@@ -289,7 +299,7 @@ class UserService {
await dialog.hide();
if (response != null && response.statusCode == 200) {
showToast(context, "Email changed to " + email);
- _config.setEmail(email);
+ await setEmail(email);
Navigator.of(context).popUntil((route) => route.isFirst);
Bus.instance.fire(UserDetailsChangedEvent());
return;
diff --git a/lib/ui/email_entry_page.dart b/lib/ui/email_entry_page.dart
index 3f0be876c4d3460911ed642b0812a88b3142a4b3..f6eddc227870ca25faef25c1bc28c00b9b3a0699 100644
--- a/lib/ui/email_entry_page.dart
+++ b/lib/ui/email_entry_page.dart
@@ -108,7 +108,7 @@ class _EmailEntryPageState extends State {
buttonText: 'Create account',
onPressedFunction: () {
_config.setVolatilePassword(_passwordController1.text);
- _config.setEmail(_email);
+ UserService.instance.setEmail(_email);
UserService.instance
.getOtt(context, _email, isCreateAccountScreen: true);
FocusScope.of(context).unfocus();
diff --git a/lib/ui/gallery.dart b/lib/ui/gallery.dart
index d4faf70c30d6e828314da8433b334077b0cf312c..adad4db34e218433028d6dafee6bf8d6b5d227b9 100644
--- a/lib/ui/gallery.dart
+++ b/lib/ui/gallery.dart
@@ -114,7 +114,7 @@ class _GalleryState extends State {
final startTime = DateTime.now().microsecondsSinceEpoch;
final result = await widget.asyncLoader(
kGalleryLoadStartTime,
- DateTime.now().microsecondsSinceEpoch,
+ kGalleryLoadEndTime,
limit: limit,
);
final endTime = DateTime.now().microsecondsSinceEpoch;
diff --git a/lib/ui/home_widget.dart b/lib/ui/home_widget.dart
index f48949dafa89668b0b0ed006172874f9e911d55a..cb84426f2eddd173f92ae43fc08225e752f14531 100644
--- a/lib/ui/home_widget.dart
+++ b/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/tab_changed_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/models/file_load_result.dart';
import 'package:photos/models/galleryType.dart';
@@ -64,7 +65,9 @@ class HomeWidget extends StatefulWidget {
class _HomeWidgetState extends State {
static const _deviceFolderGalleryWidget = CollectionsGalleryWidget();
static const _sharedCollectionGallery = SharedCollectionGallery();
- static const _settingsPage = SettingsPage();
+ static final _settingsPage = SettingsPage(
+ emailNotifier: UserService.instance.emailValueNotifier,
+ );
static const _headerWidget = HeaderWidget();
final _logger = Logger("HomeWidgetState");
@@ -566,7 +569,7 @@ class _HomeBottomNavigationBarState extends State {
_tabChangedEventSubscription =
Bus.instance.on().listen((event) {
if (event.source != TabChangedEventSource.tab_bar) {
- _logger.fine('index changed to ${event.selectedIndex}');
+ debugPrint('index changed to ${event.selectedIndex}');
if (mounted) {
setState(() {
currentTabIndex = event.selectedIndex;
diff --git a/lib/ui/login_page.dart b/lib/ui/login_page.dart
index 7eec2faf026ed977a301218bc3dbc18616b5e24a..7d62c7ad50c22aff24ba5bc55873cfa9fee1e642 100644
--- a/lib/ui/login_page.dart
+++ b/lib/ui/login_page.dart
@@ -55,7 +55,7 @@ class _LoginPageState extends State {
isFormValid: _emailIsValid,
buttonText: 'Log in',
onPressedFunction: () {
- _config.setEmail(_email);
+ UserService.instance.setEmail(_email);
UserService.instance
.getOtt(context, _email, isCreateAccountScreen: false);
FocusScope.of(context).unfocus();
diff --git a/lib/ui/memories_widget.dart b/lib/ui/memories_widget.dart
index e63fc7f92530fc2fdbdfa7d313bf4f92d5b751f8..34d225dd9493c9306d7bea506e6c1606964e3166 100644
--- a/lib/ui/memories_widget.dart
+++ b/lib/ui/memories_widget.dart
@@ -334,8 +334,8 @@ class _FullScreenMemoryState extends State {
child: IconButton(
icon: Icon(
Icons.adaptive.share,
- color: Colors.white,
- ), //same for both themes
+ color: Colors.white, //same for both themes
+ ),
onPressed: () {
share(context, [file]);
},
diff --git a/lib/ui/ott_verification_page.dart b/lib/ui/ott_verification_page.dart
index 6892d346819e75001bf4656a4f526c6144b00479..2dd66aa19531e69c046efaf7c8ea6846ec182e35 100644
--- a/lib/ui/ott_verification_page.dart
+++ b/lib/ui/ott_verification_page.dart
@@ -12,7 +12,7 @@ class OTTVerificationPage extends StatefulWidget {
OTTVerificationPage(
this.email, {
this.isChangeEmail = false,
- this.isCreateAccountScreen,
+ this.isCreateAccountScreen = false,
Key key,
}) : super(key: key);
diff --git a/lib/ui/settings/account_section_widget.dart b/lib/ui/settings/account_section_widget.dart
index 601ef397272f57132094fc63962ccf376ba17473..2575fd85176189c5e048c41ddbcc78112372d664 100644
--- a/lib/ui/settings/account_section_widget.dart
+++ b/lib/ui/settings/account_section_widget.dart
@@ -70,7 +70,7 @@ class AccountSectionWidgetState extends State {
child:
SettingsTextItem(text: "Recovery key", icon: Icons.navigate_next),
),
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
@@ -95,7 +95,7 @@ class AccountSectionWidgetState extends State {
child:
SettingsTextItem(text: "Change email", icon: Icons.navigate_next),
),
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
diff --git a/lib/ui/settings/backup_section_widget.dart b/lib/ui/settings/backup_section_widget.dart
index e6c59fa2580ebe91b2f051dd42a8efa0bade84cd..998fe120ff72488922ce261954d508948371d56e 100644
--- a/lib/ui/settings/backup_section_widget.dart
+++ b/lib/ui/settings/backup_section_widget.dart
@@ -55,7 +55,7 @@ class BackupSectionWidgetState extends State {
icon: Icons.navigate_next,
),
),
- SectionOptionDivider,
+ sectionOptionDivider,
SizedBox(
height: 48,
child: Row(
@@ -75,7 +75,7 @@ class BackupSectionWidgetState extends State {
],
),
),
- SectionOptionDivider,
+ sectionOptionDivider,
SizedBox(
height: 48,
child: Row(
@@ -98,7 +98,7 @@ class BackupSectionWidgetState extends State {
];
if (Platform.isIOS) {
sectionOptions.addAll([
- SectionOptionDivider,
+ sectionOptionDivider,
SizedBox(
height: 48,
child: Row(
@@ -134,7 +134,7 @@ class BackupSectionWidgetState extends State {
}
sectionOptions.addAll(
[
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
@@ -168,7 +168,7 @@ class BackupSectionWidgetState extends State {
icon: Icons.navigate_next,
),
),
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
diff --git a/lib/ui/settings/common_settings.dart b/lib/ui/settings/common_settings.dart
index 2512834eb8ab7b0e2e48ca339c0b25ef5c97c8f5..09fdeae3ca3fa5e56f51d0455d8921807dadb922 100644
--- a/lib/ui/settings/common_settings.dart
+++ b/lib/ui/settings/common_settings.dart
@@ -4,7 +4,7 @@ import 'package:expandable/expandable.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
-Widget SectionOptionDivider = Padding(
+Widget sectionOptionDivider = Padding(
padding: EdgeInsets.all(Platform.isIOS ? 4 : 2),
);
diff --git a/lib/ui/settings/danger_section_widget.dart b/lib/ui/settings/danger_section_widget.dart
index 4fa53c01e4b14fba6a056a65471584674ab23133..4c35878cc1deedaace22a893c4774ecb94a6165b 100644
--- a/lib/ui/settings/danger_section_widget.dart
+++ b/lib/ui/settings/danger_section_widget.dart
@@ -35,7 +35,7 @@ class _DangerSectionWidgetState extends State {
},
child: SettingsTextItem(text: "Logout", icon: Icons.navigate_next),
),
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
diff --git a/lib/ui/settings/info_section_widget.dart b/lib/ui/settings/info_section_widget.dart
index 401eef8d3bd8b23e2984779a11adf851ecaead71..13bf1414ad2bfc2c08745872c38a8cc7aa642929 100644
--- a/lib/ui/settings/info_section_widget.dart
+++ b/lib/ui/settings/info_section_widget.dart
@@ -39,7 +39,7 @@ class InfoSectionWidget extends StatelessWidget {
},
child: SettingsTextItem(text: "FAQ", icon: Icons.navigate_next),
),
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
@@ -53,7 +53,7 @@ class InfoSectionWidget extends StatelessWidget {
},
child: SettingsTextItem(text: "Terms", icon: Icons.navigate_next),
),
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
@@ -67,19 +67,19 @@ class InfoSectionWidget extends StatelessWidget {
},
child: SettingsTextItem(text: "Privacy", icon: Icons.navigate_next),
),
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
- launch("https://github.com/ente-io/frame");
+ launchUrl(Uri.parse("https://github.com/ente-io/frame"));
},
child:
SettingsTextItem(text: "Source code", icon: Icons.navigate_next),
),
+ sectionOptionDivider,
UpdateService.instance.isIndependent()
? Column(
children: [
- Divider(height: 4),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
@@ -110,7 +110,7 @@ class InfoSectionWidget extends StatelessWidget {
),
],
)
- : Container(),
+ : const SizedBox.shrink(),
],
);
}
diff --git a/lib/ui/settings/security_section_widget.dart b/lib/ui/settings/security_section_widget.dart
index 62139d4d82756eeb9fdaf14864ea81fc31fd3a61..aab9ede10056742e6c6bb8103c58617012695843 100644
--- a/lib/ui/settings/security_section_widget.dart
+++ b/lib/ui/settings/security_section_widget.dart
@@ -116,7 +116,7 @@ class _SecuritySectionWidgetState extends State {
);
}
children.addAll([
- SectionOptionDivider,
+ sectionOptionDivider,
SizedBox(
height: 48,
child: Row(
@@ -150,7 +150,7 @@ class _SecuritySectionWidgetState extends State {
if (Platform.isAndroid) {
children.addAll(
[
- SectionOptionDivider,
+ sectionOptionDivider,
SizedBox(
height: 48,
child: Row(
@@ -246,7 +246,7 @@ class _SecuritySectionWidgetState extends State {
);
}
children.addAll([
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
diff --git a/lib/ui/settings/social_section_widget.dart b/lib/ui/settings/social_section_widget.dart
index bb8e0aeb7d0c681a873e514bc8fe9ae9e12e9a99..d5633691828b6a0499f37e64d902c5dee25c110d 100644
--- a/lib/ui/settings/social_section_widget.dart
+++ b/lib/ui/settings/social_section_widget.dart
@@ -30,7 +30,7 @@ class SocialSectionWidget extends StatelessWidget {
},
child: SettingsTextItem(text: "Twitter", icon: Icons.navigate_next),
),
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
@@ -38,7 +38,7 @@ class SocialSectionWidget extends StatelessWidget {
},
child: SettingsTextItem(text: "Discord", icon: Icons.navigate_next),
),
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
@@ -50,7 +50,7 @@ class SocialSectionWidget extends StatelessWidget {
if (!UpdateService.instance.isIndependent()) {
options.addAll(
[
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
diff --git a/lib/ui/settings/support_section_widget.dart b/lib/ui/settings/support_section_widget.dart
index afebbbe83c9e4e2913d710af8cf5439feb916876..beb6fc6a101d4a7dfcc3d97f01f5fe63601a1dc2 100644
--- a/lib/ui/settings/support_section_widget.dart
+++ b/lib/ui/settings/support_section_widget.dart
@@ -43,7 +43,7 @@ class SupportSectionWidget extends StatelessWidget {
},
child: SettingsTextItem(text: "Email", icon: Icons.navigate_next),
),
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
@@ -63,7 +63,7 @@ class SupportSectionWidget extends StatelessWidget {
},
child: SettingsTextItem(text: "Roadmap", icon: Icons.navigate_next),
),
- SectionOptionDivider,
+ sectionOptionDivider,
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
diff --git a/lib/ui/settings/theme_switch_widget.dart b/lib/ui/settings/theme_switch_widget.dart
index ed669bfe42023c2e8de2ef70029ff9b46e61b314..d5466f925c80ce8465c258e9a1e261f7772f8a17 100644
--- a/lib/ui/settings/theme_switch_widget.dart
+++ b/lib/ui/settings/theme_switch_widget.dart
@@ -1,30 +1,52 @@
import 'package:adaptive_theme/adaptive_theme.dart';
+import 'package:animated_toggle_switch/animated_toggle_switch.dart';
import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
+import 'package:photos/ente_theme_data.dart';
class ThemeSwitchWidget extends StatelessWidget {
const ThemeSwitchWidget({Key key}) : super(key: key);
@override
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.rolling(
+ current: selectedTheme,
+ values: const [0, 1],
+ onChanged: (i) {
+ print("Changed to ${i}, selectedTheme is ${selectedTheme} ");
+ if (i == 0) {
AdaptiveTheme.of(context).setLight();
} else {
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,
);
}
}
diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart
index 85c85ddd38ba76825de85c12e20d72d44d2b3e48..9dace5b0ff45d206aab37be4e449af3401c1fce4 100644
--- a/lib/ui/settings_page.dart
+++ b/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';
class SettingsPage extends StatelessWidget {
- const SettingsPage({Key key}) : super(key: key);
+ final ValueNotifier emailNotifier;
+ const SettingsPage({Key key, @required this.emailNotifier}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -27,7 +28,6 @@ class SettingsPage extends StatelessWidget {
Widget _getBody(BuildContext context) {
final hasLoggedIn = Configuration.instance.getToken() != null;
- final String email = Configuration.instance.getEmail();
final List contents = [];
contents.add(
Container(
@@ -36,13 +36,23 @@ class SettingsPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
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)
? ThemeSwitchWidget()
: const SizedBox.shrink(),
diff --git a/lib/utils/email_util.dart b/lib/utils/email_util.dart
index e237e883fe51a9226fe060cde97278a13671e84e..070344e94231decd8ea57af256227c86dfecc611 100644
--- a/lib/utils/email_util.dart
+++ b/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:logging/logging.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/ente_theme_data.dart';
import 'package:photos/ui/common/dialogs.dart';
@@ -80,7 +81,7 @@ Future sendLogs(
content.addAll(
[
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(
height: 1.5,
fontSize: 16,
@@ -139,9 +140,11 @@ Future _sendLogs(
Future getZippedLogsFile(BuildContext context) async {
final dialog = createProgressDialog(context, "Preparing logs...");
await dialog.show();
+ final logsPath = (await getApplicationSupportDirectory()).path;
+ final logsDirectory = Directory(logsPath + "/logs");
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();
encoder.create(zipFilePath);
encoder.addDirectory(logsDirectory);
diff --git a/lib/utils/file_uploader.dart b/lib/utils/file_uploader.dart
index 8082ca80b5f4b5babd87f360f9797212bebbd46f..1ba235daea6a13a63b78b5428d365fc75eaab0ae 100644
--- a/lib/utils/file_uploader.dart
+++ b/lib/utils/file_uploader.dart
@@ -467,6 +467,8 @@ class FileUploader {
} catch (e, s) {
if (!(e is NoActiveSubscriptionError ||
e is StorageLimitExceededError ||
+ e is WiFiUnavailableError ||
+ e is SilentlyCancelUploadsError ||
e is FileTooLargeForPlanError)) {
_logger.severe("File upload failed for " + file.toString(), e, s);
}
@@ -650,20 +652,20 @@ class FileUploader {
Future _getUploadURL() async {
if (_uploadURLs.isEmpty) {
- await _fetchUploadURLs();
+ await fetchUploadURLs(_queue.length);
}
return _uploadURLs.removeFirst();
}
Future _uploadURLFetchInProgress;
- Future _fetchUploadURLs() async {
+ Future fetchUploadURLs(int fileCount) async {
_uploadURLFetchInProgress ??= Future(() async {
try {
final response = await _dio.get(
Configuration.instance.getHttpEndpoint() + "/files/upload-urls",
queryParameters: {
- "count": min(42, 2 * _queue.length), // m4gic number
+ "count": min(42, fileCount * 2), // m4gic number
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
diff --git a/pubspec.lock b/pubspec.lock
index e6d3f4fee345c61318c9b4968e84ace68cf9dadf..77d3c1b8a0b4bb133c0154542bd1976cfb001b0d 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -24,6 +24,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index cf045ad8f34b7a189cadeb585a790821325ff6cb..3030ed0ca88117f2fc34f0c91b021b9fbd1ff32c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -21,6 +21,7 @@ dependencies:
alice:
git: "https://github.com/jhomlala/alice.git"
animate_do: ^2.0.0
+ animated_toggle_switch: ^0.5.2
archive: ^3.1.2
background_fetch: ^1.0.1
bip39: ^1.0.6