Merge branch 'master' into photo_manager_update

This commit is contained in:
Neeraj Gupta 2022-10-19 11:48:47 +05:30
commit 86ac665365
No known key found for this signature in database
GPG key ID: 3C5A1684DC1729E1
64 changed files with 1299 additions and 1545 deletions

View file

@ -23,10 +23,10 @@ jobs:
java-version: '11'
# Setup the flutter environment.
- uses: subosito/flutter-action@v1
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.3.0'
flutter-version: '3.0.0'
# Fetch sub modules
- run: git submodule update --init --recursive
@ -50,7 +50,7 @@ jobs:
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
- name: Checksum
run: sha256sum build/app/outputs/flutter-apk/ente.apk > build/app/outputs/flutter-apk/checksum
run: sha256sum build/app/outputs/flutter-apk/ente.apk > build/app/outputs/flutter-apk/sha256sum
# Upload generated apk to the artifacts.
- uses: actions/upload-artifact@v2
@ -66,5 +66,5 @@ jobs:
# Create a Github release
- uses: ncipollo/release-action@v1
with:
artifacts: "build/app/outputs/flutter-apk/ente.apk,build/app/outputs/flutter-apk/checksum"
artifacts: "build/app/outputs/flutter-apk/ente.apk,build/app/outputs/flutter-apk/sha256sum"
token: ${{ secrets.GITHUB_TOKEN }}

5
.gitmodules vendored
View file

@ -6,11 +6,6 @@
path = thirdparty/plugins
url = https://github.com/ente-io/plugins.git
[submodule "thirdparty/sentry-dart"]
path = thirdparty/sentry-dart
url = https://github.com/ente-io/sentry-dart.git
branch = sentry_flutter_ente
[submodule "thirdparty/extended_image"]
path = thirdparty/extended_image
url = https://github.com/ente-io/extended_image.git

7
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,7 @@
{
"workbench.colorCustomizations": {
"activityBar.background": "#123046",
"titleBar.activeBackground": "#194363",
"titleBar.activeForeground": "#F9FBFD"
}
}

View file

@ -1,5 +1,5 @@
PODS:
- background_fetch (1.1.0):
- background_fetch (1.1.1):
- Flutter
- connectivity (0.0.1):
- Flutter
@ -100,7 +100,7 @@ PODS:
- GoogleUtilities/Logger
- image_editor (1.0.0):
- Flutter
- in_app_purchase (0.0.1):
- in_app_purchase_storekit (0.0.1):
- Flutter
- libwebp (1.2.3):
- libwebp/demux (= 1.2.3)
@ -116,6 +116,8 @@ PODS:
- Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0)
- media_extension (0.0.1):
- Flutter
- motionphoto (0.0.1):
- Flutter
- move_to_background (0.0.1):
@ -145,13 +147,13 @@ PODS:
- SDWebImageWebPCoder (0.9.1):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.13)
- Sentry (7.25.1):
- Sentry/Core (= 7.25.1)
- Sentry/Core (7.25.1)
- Sentry (7.27.1):
- Sentry/Core (= 7.27.1)
- Sentry/Core (7.27.1)
- sentry_flutter (0.0.1):
- Flutter
- FlutterMacOS
- Sentry (~> 7.25.1)
- Sentry (~> 7.27.1)
- share_plus (0.0.1):
- Flutter
- shared_preferences_ios (0.0.1):
@ -190,8 +192,9 @@ DEPENDENCIES:
- flutter_sodium (from `.symlinks/plugins/flutter_sodium/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- image_editor (from `.symlinks/plugins/image_editor/ios`)
- in_app_purchase (from `.symlinks/plugins/in_app_purchase/ios`)
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/ios`)
- local_auth (from `.symlinks/plugins/local_auth/ios`)
- media_extension (from `.symlinks/plugins/media_extension/ios`)
- motionphoto (from `.symlinks/plugins/motionphoto/ios`)
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
- open_mail_app (from `.symlinks/plugins/open_mail_app/ios`)
@ -266,10 +269,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/fluttertoast/ios"
image_editor:
:path: ".symlinks/plugins/image_editor/ios"
in_app_purchase:
:path: ".symlinks/plugins/in_app_purchase/ios"
in_app_purchase_storekit:
:path: ".symlinks/plugins/in_app_purchase_storekit/ios"
local_auth:
:path: ".symlinks/plugins/local_auth/ios"
media_extension:
:path: ".symlinks/plugins/media_extension/ios"
motionphoto:
:path: ".symlinks/plugins/motionphoto/ios"
move_to_background:
@ -304,7 +309,7 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock/ios"
SPEC CHECKSUMS:
background_fetch: 3795af8a49054dc526477cc2f60d2ed41de60587
background_fetch: ef7bc433c96131e4f284d8616d2e0d4e18fa6af4
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
device_info: d7d233b645a32c40dfdc212de5cf646ca482f175
Firebase: 800f16f07af493d98d017446a315c27af0552f41
@ -330,10 +335,11 @@ SPEC CHECKSUMS:
GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f
GoogleUtilities: 1d20a6ad97ef46f67bbdec158ce00563a671ebb7
image_editor: eab82a302a6623a866da5145b7c4c0ee8a4ffbb4
in_app_purchase: 3e2155afa9d03d4fa32d9e62d567885080ce97d6
in_app_purchase_storekit: d7fcf4646136ec258e237872755da8ea6c1b6096
libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c
local_auth: 1740f55d7af0a2e2a8684ce225fe79d8931e808c
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
media_extension: 6d30dc1431ebaa63f43c397c37917b1a0a597a4c
motionphoto: d4a432b8c8f22fb3ad966258597c0103c9c5ff16
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431
@ -347,8 +353,8 @@ SPEC CHECKSUMS:
receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
SDWebImage: e5cc87bf736e60f49592f307bdf9e157189298a3
SDWebImageWebPCoder: 18503de6621dd2c420d680e33d46bf8e1d5169b0
Sentry: dd29c18c32b0af9269949f079cf631d581ca76ca
sentry_flutter: 544b23de27343d0cd12d8d16b0fac71dc884f0e6
Sentry: bc644307e2eb6a4c9c55cf117a80b895bb2a25a7
sentry_flutter: 649559f0512e00d3f6fc92cf51f74bc2fe68d1d3
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904

View file

@ -286,9 +286,10 @@
"${BUILT_PRODUCTS_DIR}/flutter_sodium/flutter_sodium.framework",
"${BUILT_PRODUCTS_DIR}/fluttertoast/fluttertoast.framework",
"${BUILT_PRODUCTS_DIR}/image_editor/image_editor.framework",
"${BUILT_PRODUCTS_DIR}/in_app_purchase/in_app_purchase.framework",
"${BUILT_PRODUCTS_DIR}/in_app_purchase_storekit/in_app_purchase_storekit.framework",
"${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework",
"${BUILT_PRODUCTS_DIR}/local_auth/local_auth.framework",
"${BUILT_PRODUCTS_DIR}/media_extension/media_extension.framework",
"${BUILT_PRODUCTS_DIR}/motionphoto/motionphoto.framework",
"${BUILT_PRODUCTS_DIR}/move_to_background/move_to_background.framework",
"${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
@ -339,9 +340,10 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_sodium.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fluttertoast.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_editor.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase_storekit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/motionphoto.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/move_to_background.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",

View file

@ -1,13 +0,0 @@
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
class ThumbnailCacheManager {
static const key = 'cached-thumbnail-data';
static CacheManager instance = CacheManager(
Config(
key,
maxNrOfCacheObjects: 2500,
),
);
}

View file

@ -393,6 +393,10 @@ class Configuration {
return _cachedToken;
}
bool isLoggedIn() {
return getToken() != null;
}
Future<void> setToken(String token) async {
_cachedToken = token;
await _preferences.setString(tokenKey, token);
@ -519,7 +523,7 @@ class Configuration {
}
bool hasConfiguredAccount() {
return getToken() != null && _key != null;
return isLoggedIn() && _key != null;
}
bool shouldBackupOverMobileData() {

View file

@ -1,17 +1,30 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:fk_user_agent/fk_user_agent.dart';
import 'package:flutter/foundation.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/constants.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
int kConnectTimeout = 15000;
class Network {
// apiEndpoint points to the Ente server's API endpoint
static const apiEndpoint = String.fromEnvironment(
"endpoint",
defaultValue: kDefaultProductionEndpoint,
);
late Dio _dio;
late Dio _enteDio;
Future<void> init() async {
await FkUserAgent.init();
final packageInfo = await PackageInfo.fromPlatform();
final preferences = await SharedPreferences.getInstance();
_dio = Dio(
BaseOptions(
connectTimeout: kConnectTimeout,
@ -22,7 +35,18 @@ class Network {
},
),
);
_dio.interceptors.add(RequestIdInterceptor());
_enteDio = Dio(
BaseOptions(
baseUrl: apiEndpoint,
connectTimeout: kConnectTimeout,
headers: {
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
'X-Client-Version': packageInfo.version,
'X-Client-Package': packageInfo.packageName,
},
),
);
_enteDio.interceptors.add(EnteRequestInterceptor(preferences));
}
Network._privateConstructor();
@ -30,13 +54,27 @@ class Network {
static Network instance = Network._privateConstructor();
Dio getDio() => _dio;
Dio get enteDio => _enteDio;
}
class RequestIdInterceptor extends Interceptor {
class EnteRequestInterceptor extends Interceptor {
final SharedPreferences _preferences;
EnteRequestInterceptor(this._preferences);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (kDebugMode) {
assert(options.baseUrl == Network.apiEndpoint,
"interceptor should only be used for API endpoint");
}
// ignore: prefer_const_constructors
options.headers.putIfAbsent("x-request-id", () => Uuid().v4().toString());
final String? tokenValue = _preferences.getString(Configuration.tokenKey);
if (tokenValue != null) {
options.headers.putIfAbsent("X-Auth-Token", () => tokenValue);
}
return super.onRequest(options, handler);
}
}

View file

@ -293,22 +293,10 @@ extension CustomColorScheme on ColorScheme {
? const Color.fromRGBO(196, 196, 196, 0.6)
: const Color.fromRGBO(255, 255, 255, 0.7);
Color get gNavBackgroundColor => brightness == Brightness.light
? const Color.fromRGBO(196, 196, 196, 0.6)
: const Color.fromRGBO(40, 40, 40, 0.6);
Color get gNavBarActiveColor => brightness == Brightness.light
? const Color.fromRGBO(255, 255, 255, 0.6)
: const Color.fromRGBO(255, 255, 255, 0.9);
Color get gNavIconColor => brightness == Brightness.light
? const Color.fromRGBO(0, 0, 0, 0.8)
: const Color.fromRGBO(255, 255, 255, 0.8);
Color get gNavActiveIconColor => brightness == Brightness.light
? const Color.fromRGBO(0, 0, 0, 0.8)
: const Color.fromRGBO(0, 0, 0, 0.8);
Color get galleryThumbBackgroundColor => brightness == Brightness.light
? const Color.fromRGBO(240, 240, 240, 1)
: const Color.fromRGBO(20, 20, 20, 1);

View file

@ -0,0 +1,3 @@
import 'package:photos/events/event.dart';
class OpenedSettingsEvent extends Event {}

View file

@ -7,7 +7,6 @@ import 'package:background_fetch/background_fetch.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:photos/app.dart';
@ -74,7 +73,7 @@ Future<void> _runInForeground() async {
});
}
Future<void> _runBackgroundTask(String taskId) async {
Future<void> _runBackgroundTask(String taskId, {String mode = 'normal'}) async {
if (_isProcessRunning) {
_logger.info("Background task triggered when process was already running");
await _sync('bgTaskActiveProcess');
@ -82,7 +81,7 @@ Future<void> _runBackgroundTask(String taskId) async {
} else {
_runWithLogs(
() async {
_logger.info("run background task");
_logger.info("Starting background task in $mode mode");
_runInBackground(taskId);
},
prefix: "[bg]",
@ -110,11 +109,14 @@ Future<void> _runInBackground(String taskId) async {
BackgroundFetch.finish(taskId);
}
// https://stackoverflow.com/a/73796478/546896
@pragma('vm:entry-point')
void _headlessTaskHandler(HeadlessTask task) {
print("_headlessTaskHandler");
if (task.timeout) {
BackgroundFetch.finish(task.taskId);
} else {
_runInBackground(task.taskId);
_runBackgroundTask(task.taskId, mode: "headless");
}
}
@ -128,7 +130,6 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
} else {
AppLifecycleService.instance.onAppInForeground('init via: $via');
}
InAppPurchaseConnection.enablePendingPurchases();
CryptoUtil.init();
await NotificationService.instance.init();
await Network.instance.init();

View file

@ -1,7 +1,6 @@
import 'package:photos/models/trash_file.dart';
const kIgnoreReasonTrash = "trash";
const kIgnoreReasonInvalidFile = "invalidFile";
class IgnoredFile {
final String? localID;

View file

@ -1,9 +1,10 @@
import 'dart:math';
import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:photos/models/subscription.dart';
class UserDetails {
class UserDetails extends Equatable {
final String email;
final int usage;
final int fileCount;
@ -11,7 +12,7 @@ class UserDetails {
final Subscription subscription;
final FamilyData? familyData;
UserDetails(
const UserDetails(
this.email,
this.usage,
this.fileCount,
@ -20,6 +21,16 @@ class UserDetails {
this.familyData,
);
@override
List<Object?> get props => [
email,
usage,
fileCount,
sharedCollectionsCount,
subscription,
familyData
];
bool isPartOfFamily() {
return familyData?.members?.isNotEmpty ?? false;
}

View file

@ -31,6 +31,7 @@ class BillingService {
final _logger = Logger("BillingService");
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
final _config = Configuration.instance;
bool _isOnSubscriptionPage = false;
@ -38,12 +39,11 @@ class BillingService {
Future<BillingPlans> _future;
Future<void> init() async {
InAppPurchaseConnection.enablePendingPurchases();
// if (Platform.isIOS && kDebugMode) {
// await FlutterInappPurchase.instance.initConnection;
// FlutterInappPurchase.instance.clearTransactionIOS();
// }
InAppPurchaseConnection.instance.purchaseUpdatedStream.listen((purchases) {
InAppPurchase.instance.purchaseStream.listen((purchases) {
if (_isOnSubscriptionPage) {
return;
}
@ -54,11 +54,11 @@ class BillingService {
purchase.verificationData.serverVerificationData,
).then((response) {
if (response != null) {
InAppPurchaseConnection.instance.completePurchase(purchase);
InAppPurchase.instance.completePurchase(purchase);
}
});
} else if (Platform.isIOS && purchase.pendingCompletePurchase) {
InAppPurchaseConnection.instance.completePurchase(purchase);
InAppPurchase.instance.completePurchase(purchase);
}
}
});
@ -69,7 +69,7 @@ class BillingService {
}
Future<BillingPlans> getBillingPlans() {
_future ??= (_config.getToken() == null
_future ??= (_config.isLoggedIn()
? _fetchPublicBillingPlans()
: _fetchPrivateBillingPlans())
.then((response) {
@ -79,14 +79,7 @@ class BillingService {
}
Future<Response<dynamic>> _fetchPrivateBillingPlans() {
return _dio.get(
_config.getHttpEndpoint() + "/billing/user-plans/",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
return _enteDio.get("/billing/user-plans/");
}
Future<Response<dynamic>> _fetchPublicBillingPlans() {
@ -99,19 +92,14 @@ class BillingService {
final paymentProvider,
}) async {
try {
final response = await _dio.post(
_config.getHttpEndpoint() + "/billing/verify-subscription",
final response = await _enteDio.post(
"/billing/verify-subscription",
data: {
"paymentProvider": paymentProvider ??
(Platform.isAndroid ? "playstore" : "appstore"),
"productID": productID,
"verificationData": verificationData,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
return Subscription.fromMap(response.data["subscription"]);
} on DioError catch (e) {
@ -128,14 +116,7 @@ class BillingService {
Future<Subscription> fetchSubscription() async {
try {
final response = await _dio.get(
_config.getHttpEndpoint() + "/billing/subscription",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response = await _enteDio.get("/billing/subscription");
final subscription = Subscription.fromMap(response.data["subscription"]);
return subscription;
} on DioError catch (e, s) {
@ -146,14 +127,8 @@ class BillingService {
Future<Subscription> cancelStripeSubscription() async {
try {
final response = await _dio.post(
_config.getHttpEndpoint() + "/billing/stripe/cancel-subscription",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response =
await _enteDio.post("/billing/stripe/cancel-subscription");
final subscription = Subscription.fromMap(response.data["subscription"]);
return subscription;
} on DioError catch (e, s) {
@ -164,14 +139,8 @@ class BillingService {
Future<Subscription> activateStripeSubscription() async {
try {
final response = await _dio.post(
_config.getHttpEndpoint() + "/billing/stripe/activate-subscription",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response =
await _enteDio.post("/billing/stripe/activate-subscription");
final subscription = Subscription.fromMap(response.data["subscription"]);
return subscription;
} on DioError catch (e, s) {
@ -184,16 +153,11 @@ class BillingService {
String endpoint = kWebPaymentRedirectUrl,
}) async {
try {
final response = await _dio.get(
_config.getHttpEndpoint() + "/billing/stripe/customer-portal",
final response = await _enteDio.get(
"/billing/stripe/customer-portal",
queryParameters: {
"redirectURL": kWebPaymentRedirectUrl,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
return response.data["url"];
} on DioError catch (e, s) {

View file

@ -46,7 +46,7 @@ class CollectionsService {
Configuration _config;
SharedPreferences _prefs;
Future<List<File>> _cachedLatestFiles;
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
final _localPathToCollectionID = <String, int>{};
final _collectionIDToCollections = <int, Collection>{};
final _cachedKeys = <int, Uint8List>{};
@ -172,16 +172,12 @@ class CollectionsService {
}
Future<List<User>> getSharees(int collectionID) {
return _dio
.get(
Configuration.instance.getHttpEndpoint() + "/collections/sharees",
return _enteDio.get(
"/collections/sharees",
queryParameters: {
"collectionID": collectionID,
},
options:
Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
)
.then((response) {
).then((response) {
_logger.info(response.toString());
final sharees = <User>[];
for (final user in response.data["sharees"]) {
@ -197,16 +193,13 @@ class CollectionsService {
Sodium.base642bin(publicKey),
);
try {
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/share",
await _enteDio.post(
"/collections/share",
data: {
"collectionID": collectionID,
"email": email,
"encryptedKey": Sodium.bin2base64(encryptedKey),
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
} on DioError catch (e) {
if (e.response.statusCode == 402) {
@ -219,15 +212,12 @@ class CollectionsService {
Future<void> unshare(int collectionID, String email) async {
try {
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/unshare",
await _enteDio.post(
"/collections/unshare",
data: {
"collectionID": collectionID,
"email": email,
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
_collectionIDToCollections[collectionID]
.sharees
@ -256,12 +246,8 @@ class CollectionsService {
await RemoteSyncService.instance
.updateDeviceFolderSyncStatus(deivcePathIDsToUnsync);
}
await _dio.delete(
Configuration.instance.getHttpEndpoint() +
"/collections/v2/${collection.id}",
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
await _enteDio.delete(
"/collections/v2/${collection.id}",
);
await _filesDB.deleteCollection(collection.id);
final deletedCollection = collection.copyWith(isDeleted: true);
@ -292,7 +278,7 @@ class CollectionsService {
Uint8List _getDecryptedKey(Collection collection) {
final encryptedKey = Sodium.base642bin(collection.encryptedKey);
if (collection.owner.id == _config.getUserID()) {
if(_config.getKey() == null) {
if (_config.getKey() == null) {
throw Exception("key can not be null");
}
return CryptoUtil.decryptSync(
@ -315,16 +301,13 @@ class CollectionsService {
utf8.encode(newName),
getCollectionKey(collection.id),
);
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/rename",
await _enteDio.post(
"/collections/rename",
data: {
"collectionID": collection.id,
"encryptedName": Sodium.bin2base64(encryptedName.encryptedData),
"nameDecryptionNonce": Sodium.bin2base64(encryptedName.nonce)
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
// trigger sync to fetch the latest name from server
sync();
@ -334,6 +317,19 @@ class CollectionsService {
}
}
Future<void> leaveAlbum(Collection collection) async {
try {
await _enteDio.post(
"/collections/leave/${collection.id}",
);
// trigger sync to fetch the latest name from server
sync();
} catch (e, s) {
_logger.severe("failed to leave collection", e, s);
rethrow;
}
}
Future<void> updateMagicMetadata(
Collection collection,
Map<String, dynamic> newMetadataUpdate,
@ -373,13 +369,9 @@ class CollectionsService {
header: Sodium.bin2base64(encryptedMMd.header),
),
);
await _dio.put(
Configuration.instance.getHttpEndpoint() +
"/collections/magic-metadata",
await _enteDio.put(
"/collections/magic-metadata",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
collection.mMdVersion = currentVersion + 1;
_cacheCollectionAttributes(collection);
@ -399,14 +391,11 @@ class CollectionsService {
Future<void> createShareUrl(Collection collection) async {
try {
final response = await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/share-url",
final response = await _enteDio.post(
"/collections/share-url",
data: {
"collectionID": collection.id,
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
await _db.insert(List.from([collection]));
@ -429,12 +418,9 @@ class CollectionsService {
) async {
prop.putIfAbsent('collectionID', () => collection.id);
try {
final response = await _dio.put(
Configuration.instance.getHttpEndpoint() + "/collections/share-url",
final response = await _enteDio.put(
"/collections/share-url",
data: json.encode(prop),
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
// remove existing url information
collection.publicURLs?.clear();
@ -455,15 +441,8 @@ class CollectionsService {
Future<void> disableShareUrl(Collection collection) async {
try {
await _dio.delete(
_config.getHttpEndpoint() +
"/collections/share-url/" +
collection.id.toString(),
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
await _enteDio.delete(
"/collections/share-url/" + collection.id.toString(),
);
collection.publicURLs.clear();
await _db.insert(List.from([collection]));
@ -477,15 +456,12 @@ class CollectionsService {
Future<List<Collection>> _fetchCollections(int sinceTime) async {
try {
final response = await _dio.get(
Configuration.instance.getHttpEndpoint() + "/collections",
final response = await _enteDio.get(
"/collections",
queryParameters: {
"sinceTime": sinceTime,
"source": AppLifecycleService.instance.isForeground ? "fg" : "bg",
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
final List<Collection> collections = [];
if (response != null) {
@ -547,11 +523,8 @@ class CollectionsService {
Future<Collection> fetchCollectionByID(int collectionID) async {
try {
_logger.fine('fetching collectionByID $collectionID');
final response = await _dio.get(
Configuration.instance.getHttpEndpoint() + "/collections/$collectionID",
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
final response = await _enteDio.get(
"/collections/$collectionID",
);
assert(response != null && response.data != null);
final collectionData = response.data["collection"];
@ -659,12 +632,9 @@ class CollectionsService {
}
try {
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/add-files",
await _enteDio.post(
"/collections/add-files",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
await _filesDB.insertMultiple(files);
Bus.instance.fire(CollectionUpdatedEvent(collectionID, files));
@ -702,12 +672,9 @@ class CollectionsService {
);
try {
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/add-files",
await _enteDio.post(
"/collections/add-files",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
localFileToUpload.collectionID = destCollectionID;
localFileToUpload.uploadedFileID = uploadedFileID;
@ -739,12 +706,9 @@ class CollectionsService {
);
}
try {
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/restore-files",
await _enteDio.post(
"/collections/restore-files",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
await _filesDB.insertMultiple(files);
await TrashDB.instance
@ -801,11 +765,9 @@ class CollectionsService {
).toMap(),
);
}
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/move-files",
await _enteDio.post(
"/collections/move-files",
data: params,
options:
Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
);
// remove files from old collection
@ -860,11 +822,9 @@ class CollectionsService {
}
params["fileIDs"].add(file.uploadedFileID);
}
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/collections/v2/remove-files",
await _enteDio.post(
"/collections/v2/remove-files",
data: params,
options:
Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
);
await _filesDB.removeFromCollection(collectionID, params["fileIDs"]);
Bus.instance.fire(CollectionUpdatedEvent(collectionID, files));
@ -873,12 +833,10 @@ class CollectionsService {
}
Future<Collection> createAndCacheCollection(Collection collection) async {
return _dio
return _enteDio
.post(
Configuration.instance.getHttpEndpoint() + "/collections",
"/collections",
data: collection.toMap(),
options:
Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
)
.then((response) {
final collection = Collection.fromMap(response.data["collection"]);
@ -954,63 +912,4 @@ class CollectionsService {
}
}
class AddFilesRequest {
final int collectionID;
final List<CollectionFileItem> files;
AddFilesRequest(
this.collectionID,
this.files,
);
AddFilesRequest copyWith({
int collectionID,
List<CollectionFileItem> files,
}) {
return AddFilesRequest(
collectionID ?? this.collectionID,
files ?? this.files,
);
}
Map<String, dynamic> toMap() {
return {
'collectionID': collectionID,
'files': files?.map((x) => x?.toMap())?.toList(),
};
}
factory AddFilesRequest.fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return AddFilesRequest(
map['collectionID'],
List<CollectionFileItem>.from(
map['files']?.map((x) => CollectionFileItem.fromMap(x)),
),
);
}
String toJson() => json.encode(toMap());
factory AddFilesRequest.fromJson(String source) =>
AddFilesRequest.fromMap(json.decode(source));
@override
String toString() =>
'AddFilesRequest(collectionID: $collectionID, files: $files)';
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is AddFilesRequest &&
o.collectionID == collectionID &&
listEquals(o.files, files);
}
@override
int get hashCode => collectionID.hashCode ^ files.hashCode;
}
class SharingNotPermittedForFreeAccountsError extends Error {}

View file

@ -1,8 +1,6 @@
// @dart=2.9
import 'package:dio/dio.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/errors.dart';
import 'package:photos/core/network.dart';
import 'package:photos/db/files_db.dart';
@ -11,7 +9,7 @@ import 'package:photos/models/file.dart';
class DeduplicationService {
final _logger = Logger("DeduplicationService");
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
DeduplicationService._privateConstructor();
@ -107,14 +105,7 @@ class DeduplicationService {
}
Future<DuplicateFilesResponse> _fetchDuplicateFileIDs() async {
final response = await _dio.get(
Configuration.instance.getHttpEndpoint() + "/files/duplicates",
options: Options(
headers: {
"X-Auth-Token": Configuration.instance.getToken(),
},
),
);
final response = await _enteDio.get("/files/duplicates");
return DuplicateFilesResponse.fromMap(response.data);
}
}

View file

@ -20,12 +20,12 @@ import 'package:photos/utils/file_download_util.dart';
class FileMagicService {
final _logger = Logger("FileMagicService");
Dio _dio;
Dio _enteDio;
FilesDB _filesDB;
FileMagicService._privateConstructor() {
_filesDB = FilesDB.instance;
_dio = Network.instance.getDio();
_enteDio = Network.instance.enteDio;
}
static final FileMagicService instance =
@ -93,14 +93,7 @@ class FileMagicService {
file.pubMmdVersion = file.pubMmdVersion + 1;
}
await _dio.put(
Configuration.instance.getHttpEndpoint() +
"/files/public-magic-metadata",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
await _enteDio.put("/files/public-magic-metadata", data: params);
// update the state of the selected file. Same file in other collection
// should be eventually synced after remote sync has completed
await _filesDB.insertMultiple(files);
@ -164,13 +157,7 @@ class FileMagicService {
file.mMdVersion = file.mMdVersion + 1;
}
await _dio.put(
Configuration.instance.getHttpEndpoint() + "/files/magic-metadata",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
await _enteDio.put("/files/magic-metadata", data: params);
// update the state of the selected file. Same file in other collection
// should be eventually synced after remote sync has completed
await _filesDB.insertMultiple(files);

View file

@ -1,31 +1,23 @@
// ignore: import_of_legacy_library_into_null_safe
import 'package:dio/dio.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/network.dart';
class FilesService {
late Configuration _config;
late Dio _dio;
late Dio _enteDio;
late Logger _logger;
FilesService._privateConstructor() {
_config = Configuration.instance;
_dio = Network.instance.getDio();
_enteDio = Network.instance.enteDio;
_logger = Logger("FilesService");
}
static final FilesService instance = FilesService._privateConstructor();
Future<int> getFileSize(int uploadedFileID) async {
try {
final response = await _dio.post(
Configuration.instance.getHttpEndpoint() + "/files/size",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
final response = await _enteDio.post(
"/files/size",
data: {
"fileIDs": [uploadedFileID],
"fileIDs": [uploadedFileID]
},
);
return response.data["size"];

View file

@ -10,6 +10,7 @@ import 'package:photos/db/file_updation_db.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/models/file.dart' as ente;
import 'package:photos/utils/file_uploader_util.dart';
import 'package:photos/utils/file_util.dart';
import 'package:shared_preferences/shared_preferences.dart';
// LocalFileUpdateService tracks all the potential local file IDs which have
@ -106,6 +107,7 @@ class LocalFileUpdateService {
_logger.info(
"Marking for file update as hash did not match ${file.tag}",
);
await clearCache(file);
await FilesDB.instance.updateUploadedFile(
file.localID,
file.title,

View file

@ -1,6 +1,5 @@
// @dart=2.9
import 'package:dio/dio.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:logging/logging.dart';
@ -22,8 +21,6 @@ class PushService {
static final PushService instance = PushService._privateConstructor();
static final _logger = Logger("PushService");
final _dio = Network.instance.getDio();
SharedPreferences _prefs;
PushService._privateConstructor();
@ -75,15 +72,12 @@ class PushService {
}
Future<void> _setPushTokenOnServer(String fcmToken, String apnsToken) async {
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/push/token",
await Network.instance.enteDio.post(
"/push/token",
data: {
"fcmToken": fcmToken,
"apnsToken": apnsToken,
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
}

View file

@ -578,6 +578,7 @@ class RemoteSyncService {
// Case [1] Check and clear local cache when uploadedFile already exist
existingFile = await _db.getFile(remoteDiff.generatedID);
if (_shouldClearCache(remoteDiff, existingFile)) {
needsGalleryReload = true;
await clearCache(remoteDiff);
}
}

View file

@ -1,8 +1,6 @@
// @dart=2.9
import 'package:dio/dio.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/core/network.dart';
import 'package:photos/data/holidays.dart';
@ -25,8 +23,7 @@ import 'package:tuple/tuple.dart';
class SearchService {
Future<List<File>> _cachedFilesFuture;
final _dio = Network.instance.getDio();
final _config = Configuration.instance;
final _enteDio = Network.instance.enteDio;
final _logger = Logger((SearchService).toString());
final _collectionService = CollectionsService.instance;
static const _maximumResultsLimit = 20;
@ -85,13 +82,9 @@ class SearchService {
final List<GenericSearchResult> searchResults = [];
try {
final List<File> allFiles = await _getAllFiles();
final response = await _dio.get(
_config.getHttpEndpoint() + "/search/location",
final response = await _enteDio.get(
"/search/location",
queryParameters: {"query": query, "limit": 10},
options: Options(
headers: {"X-Auth-Token": _config.getToken()},
),
);
final matchedLocationSearchResults =

View file

@ -29,7 +29,7 @@ class SyncService {
final _logger = Logger("SyncService");
final _localSyncService = LocalSyncService.instance;
final _remoteSyncService = RemoteSyncService.instance;
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
final _uploader = FileUploader.instance;
bool _syncStopRequested = false;
Completer<bool> _existingSync;
@ -198,13 +198,8 @@ class SyncService {
}
Future<void> deleteFilesOnServer(List<int> fileIDs) async {
return await _dio.post(
Configuration.instance.getHttpEndpoint() + "/files/delete",
options: Options(
headers: {
"X-Auth-Token": Configuration.instance.getToken(),
},
),
return await _enteDio.post(
"/files/delete",
data: {
"fileIDs": fileIDs,
},
@ -219,16 +214,9 @@ class SyncService {
Future<int> _getFileSize(List<int> fileIDs) async {
try {
final response = await _dio.post(
Configuration.instance.getHttpEndpoint() + "/files/size",
options: Options(
headers: {
"X-Auth-Token": Configuration.instance.getToken(),
},
),
data: {
"fileIDs": fileIDs,
},
final response = await _enteDio.post(
"/files/size",
data: {"fileIDs": fileIDs},
);
return response.data["size"];
} catch (e) {

View file

@ -4,7 +4,6 @@ import 'dart:async';
import 'package:dio/dio.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/core/network.dart';
import 'package:photos/db/trash_db.dart';
@ -31,7 +30,7 @@ class TrashSyncService {
static final TrashSyncService instance =
TrashSyncService._privateConstructor();
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
@ -129,13 +128,8 @@ class TrashSyncService {
Future<Response<dynamic>> _trashFiles(
Map<String, dynamic> requestData,
) async {
return _dio.post(
Configuration.instance.getHttpEndpoint() + "/files/trash",
options: Options(
headers: {
"X-Auth-Token": Configuration.instance.getToken(),
},
),
return _enteDio.post(
"/files/trash",
data: requestData,
);
}
@ -148,13 +142,8 @@ class TrashSyncService {
params["fileIDs"].add(fileID);
}
try {
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/trash/delete",
options: Options(
headers: {
"X-Auth-Token": Configuration.instance.getToken(),
},
),
await _enteDio.post(
"/trash/delete",
data: params,
);
await _trashDB.delete(uniqueFileIds);
@ -171,13 +160,8 @@ class TrashSyncService {
final params = <String, dynamic>{};
params["lastUpdatedAt"] = _getSyncTime();
try {
await _dio.post(
Configuration.instance.getHttpEndpoint() + "/trash/empty",
options: Options(
headers: {
"X-Auth-Token": Configuration.instance.getToken(),
},
),
await _enteDio.post(
"/trash/empty",
data: params,
);
await _trashDB.clearTable();

View file

@ -2,9 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/core/network.dart';
import 'package:photos/events/notification_event.dart';
@ -12,9 +10,8 @@ import 'package:photos/services/user_service.dart';
import 'package:shared_preferences/shared_preferences.dart';
class UserRemoteFlagService {
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
final _logger = Logger((UserRemoteFlagService).toString());
final _config = Configuration.instance;
late SharedPreferences _prefs;
UserRemoteFlagService._privateConstructor();
@ -95,15 +92,8 @@ class UserRemoteFlagService {
if (defaultValue != null) {
queryParams["defaultValue"] = defaultValue;
}
final response = await _dio.get(
_config.getHttpEndpoint() + "/remote-store",
queryParameters: queryParams,
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response =
await _enteDio.get("/remote-store", queryParameters: queryParams);
if (response.statusCode != HttpStatus.ok) {
throw Exception("Unexpected status code ${response.statusCode}");
}
@ -118,17 +108,12 @@ class UserRemoteFlagService {
// to mark recovery as completed
Future<void> _updateKeyValue(String key, String value) async {
try {
final response = await _dio.post(
_config.getHttpEndpoint() + "/remote-store/update",
final response = await _enteDio.post(
"/remote-store/update",
data: {
"key": key,
"value": value,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
if (response.statusCode != HttpStatus.ok) {
throw Exception("Unexpected state");

View file

@ -34,6 +34,7 @@ import 'package:photos/utils/toast_util.dart';
class UserService {
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
final _logger = Logger((UserService).toString());
final _config = Configuration.instance;
ValueNotifier<String> emailValueNotifier;
@ -92,14 +93,9 @@ class UserService {
Future<String> getPublicKey(String email) async {
try {
final response = await _dio.get(
_config.getHttpEndpoint() + "/users/public-key",
final response = await _enteDio.get(
"/users/public-key",
queryParameters: {"email": email},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final publicKey = response.data["publicKey"];
await PublicKeysDB.instance.setKey(PublicKey(email, publicKey));
@ -112,17 +108,11 @@ class UserService {
Future<UserDetails> getUserDetailsV2({bool memoryCount = true}) async {
try {
final response = await _dio.get(
_config.getHttpEndpoint() +
"/users/details/v2?memoryCount=$memoryCount",
final response = await _enteDio.get(
"/users/details/v2?memoryCount=$memoryCount",
queryParameters: {
"memoryCount": memoryCount,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
return UserDetails.fromMap(response.data);
} on DioError catch (e) {
@ -133,14 +123,7 @@ class UserService {
Future<Sessions> getActiveSessions() async {
try {
final response = await _dio.get(
_config.getHttpEndpoint() + "/users/sessions",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response = await _enteDio.get("/users/sessions");
return Sessions.fromMap(response.data);
} on DioError catch (e) {
_logger.info(e);
@ -150,13 +133,8 @@ class UserService {
Future<void> terminateSession(String token) async {
try {
await _dio.delete(
_config.getHttpEndpoint() + "/users/session",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
await _enteDio.delete(
"/users/session",
queryParameters: {
"token": token,
},
@ -169,14 +147,7 @@ class UserService {
Future<void> leaveFamilyPlan() async {
try {
await _dio.delete(
_config.getHttpEndpoint() + "/family/leave",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
await _enteDio.delete("/family/leave");
} on DioError catch (e) {
_logger.warning('failed to leave family plan', e);
rethrow;
@ -187,14 +158,7 @@ class UserService {
final dialog = createProgressDialog(context, "Logging out...");
await dialog.show();
try {
final response = await _dio.post(
_config.getHttpEndpoint() + "/users/logout",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response = await _enteDio.post("/users/logout");
if (response != null && response.statusCode == 200) {
await Configuration.instance.logout();
await dialog.hide();
@ -215,14 +179,7 @@ class UserService {
final dialog = createProgressDialog(context, "Please wait...");
await dialog.show();
try {
final response = await _dio.get(
_config.getHttpEndpoint() + "/users/delete-challenge",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response = await _enteDio.get("/users/delete-challenge");
if (response != null && response.statusCode == 200) {
// clear data
await dialog.hide();
@ -248,16 +205,11 @@ class UserService {
final dialog = createProgressDialog(context, "Deleting account...");
await dialog.show();
try {
final response = await _dio.delete(
_config.getHttpEndpoint() + "/users/delete",
final response = await _enteDio.delete(
"/users/delete",
data: {
"challenge": challengeResponse,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
if (response != null && response.statusCode == 200) {
// clear data
@ -353,17 +305,12 @@ class UserService {
final dialog = createProgressDialog(context, "Please wait...");
await dialog.show();
try {
final response = await _dio.post(
_config.getHttpEndpoint() + "/users/change-email",
final response = await _enteDio.post(
"/users/change-email",
data: {
"email": email,
"ott": ott,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
await dialog.hide();
if (response != null && response.statusCode == 200) {
@ -395,17 +342,12 @@ class UserService {
Future<void> setAttributes(KeyGenResult result) async {
try {
final name = _config.getName();
await _dio.put(
_config.getHttpEndpoint() + "/users/attributes",
await _enteDio.put(
"/users/attributes",
data: {
"name": name,
"keyAttributes": result.keyAttributes.toMap(),
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
await _config.setKey(result.privateKeyAttributes.key);
await _config.setSecretKey(result.privateKeyAttributes.secretKey);
@ -425,14 +367,9 @@ class UserService {
memLimit: keyAttributes.memLimit,
opsLimit: keyAttributes.opsLimit,
);
await _dio.put(
_config.getHttpEndpoint() + "/users/keys",
await _enteDio.put(
"/users/keys",
data: setKeyRequest.toMap(),
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
await _config.setKeyAttributes(keyAttributes);
} catch (e) {
@ -449,14 +386,9 @@ class UserService {
keyAttributes.recoveryKeyEncryptedWithMasterKey,
keyAttributes.recoveryKeyDecryptionNonce,
);
await _dio.put(
_config.getHttpEndpoint() + "/users/recovery-key",
await _enteDio.put(
"/users/recovery-key",
data: setRecoveryKeyRequest.toMap(),
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
await _config.setKeyAttributes(keyAttributes);
} catch (e) {
@ -661,14 +593,7 @@ class UserService {
final dialog = createProgressDialog(context, "Please wait...");
await dialog.show();
try {
final response = await _dio.post(
_config.getHttpEndpoint() + "/users/two-factor/setup",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response = await _enteDio.post("/users/two-factor/setup");
await dialog.hide();
routeToPage(
context,
@ -701,8 +626,8 @@ class UserService {
final encryptionResult =
CryptoUtil.encryptSync(Sodium.base642bin(secret), recoveryKey);
try {
await _dio.post(
_config.getHttpEndpoint() + "/users/two-factor/enable",
await _enteDio.post(
"/users/two-factor/enable",
data: {
"code": code,
"encryptedTwoFactorSecret":
@ -710,11 +635,6 @@ class UserService {
"twoFactorSecretDecryptionNonce":
Sodium.bin2base64(encryptionResult.nonce),
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
await dialog.hide();
Navigator.pop(context);
@ -747,13 +667,8 @@ class UserService {
createProgressDialog(context, "Disabling two-factor authentication...");
await dialog.show();
try {
await _dio.post(
_config.getHttpEndpoint() + "/users/two-factor/disable",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
await _enteDio.post(
"/users/two-factor/disable",
);
Bus.instance.fire(TwoFactorStatusChangeEvent(false));
await dialog.hide();
@ -771,14 +686,7 @@ class UserService {
Future<bool> fetchTwoFactorStatus() async {
try {
final response = await _dio.get(
_config.getHttpEndpoint() + "/users/two-factor/status",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response = await _enteDio.get("/users/two-factor/status");
return response.data["status"];
} catch (e) {
_logger.severe("Failed to fetch 2FA status", e);
@ -808,14 +716,7 @@ class UserService {
Future<String> getPaymentToken() async {
try {
final response = await _dio.get(
"${_config.getHttpEndpoint()}/users/payment-token",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response = await _enteDio.get("/users/payment-token");
if (response != null && response.statusCode == 200) {
return response.data["paymentToken"];
} else {
@ -829,14 +730,7 @@ class UserService {
Future<String> getFamiliesToken() async {
try {
final response = await _dio.get(
"${_config.getHttpEndpoint()}/users/families-token",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response = await _enteDio.get("/users/families-token");
if (response != null && response.statusCode == 200) {
return response.data["familiesToken"];
} else {

View file

@ -0,0 +1,90 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/opened_settings_event.dart';
import 'package:photos/events/user_details_changed_event.dart';
import 'package:photos/models/user_details.dart';
// ignore: import_of_legacy_library_into_null_safe
import 'package:photos/services/user_service.dart';
class UserDetailsStateWidget extends StatefulWidget {
final Widget child;
const UserDetailsStateWidget({
required this.child,
Key? key,
}) : super(key: key);
@override
State<UserDetailsStateWidget> createState() => UserDetailsStateWidgetState();
}
class UserDetailsStateWidgetState extends State<UserDetailsStateWidget> {
late Future<UserDetails?> userDetails;
late StreamSubscription<UserDetailsChangedEvent> _userDetailsChangedEvent;
late StreamSubscription<OpenedSettingsEvent> _openedSettingsEventSubscription;
@override
void initState() {
if (Configuration.instance.hasConfiguredAccount()) {
_fetchUserDetails();
} else {
userDetails = Future.value(null);
}
_userDetailsChangedEvent =
Bus.instance.on<UserDetailsChangedEvent>().listen((event) {
_fetchUserDetails();
});
_openedSettingsEventSubscription =
Bus.instance.on<OpenedSettingsEvent>().listen((event) {
Future.delayed(
const Duration(
seconds: 1,
),
_fetchUserDetails,
);
});
super.initState();
}
@override
void dispose() {
_userDetailsChangedEvent.cancel();
_openedSettingsEventSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) => InheritedUserDetails(
userDetailsState: this,
userDetails: userDetails,
child: widget.child,
);
void _fetchUserDetails() {
userDetails = UserService.instance.getUserDetailsV2(memoryCount: true);
if (mounted) {
setState(() {});
}
}
}
class InheritedUserDetails extends InheritedWidget {
final UserDetailsStateWidgetState userDetailsState;
final Future<UserDetails?> userDetails;
const InheritedUserDetails({
Key? key,
required Widget child,
required this.userDetails,
required this.userDetailsState,
}) : super(key: key, child: child);
static InheritedUserDetails? of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<InheritedUserDetails>();
@override
bool updateShouldNotify(covariant InheritedUserDetails oldWidget) =>
userDetails != oldWidget.userDetails;
}

View file

@ -26,6 +26,7 @@ class EnteColorScheme {
final Color strokeBase;
final Color strokeMuted;
final Color strokeFaint;
final Color strokeFainter;
// Fixed Colors
final Color primary700;
@ -33,11 +34,15 @@ class EnteColorScheme {
final Color primary400;
final Color primary300;
//warning colors
final Color warning700;
final Color warning500;
final Color warning400;
final Color caution500;
//other colors
final Color tabIcon;
const EnteColorScheme(
this.backgroundBase,
this.backgroundElevated,
@ -52,7 +57,9 @@ class EnteColorScheme {
this.fillFaint,
this.strokeBase,
this.strokeMuted,
this.strokeFaint, {
this.strokeFaint,
this.strokeFainter,
this.tabIcon, {
this.primary700 = _primary700,
this.primary500 = _primary500,
this.primary400 = _primary400,
@ -79,6 +86,8 @@ const EnteColorScheme lightScheme = EnteColorScheme(
strokeBaseLight,
strokeMutedLight,
strokeFaintLight,
strokeFainterLight,
tabIconLight,
);
const EnteColorScheme darkScheme = EnteColorScheme(
@ -96,6 +105,8 @@ const EnteColorScheme darkScheme = EnteColorScheme(
strokeBaseDark,
strokeMutedDark,
strokeFaintDark,
strokeFainterDark,
tabIconDark,
);
// Background Colors
@ -135,11 +146,18 @@ const Color fillFaintDark = Color.fromRGBO(255, 255, 255, 0.12);
// Stroke Colors
const Color strokeBaseLight = Color.fromRGBO(0, 0, 0, 1);
const Color strokeMutedLight = Color.fromRGBO(0, 0, 0, 0.24);
const Color strokeFaintLight = Color.fromRGBO(0, 0, 0, 0.04);
const Color strokeFaintLight = Color.fromRGBO(0, 0, 0, 0.12);
const Color strokeFainterLight = Color.fromRGBO(0, 0, 0, 0.06);
const Color strokeBaseDark = Color.fromRGBO(255, 255, 255, 1);
const Color strokeMutedDark = Color.fromRGBO(255, 255, 255, 0.24);
const Color strokeFaintDark = Color.fromRGBO(255, 255, 255, 0.16);
const Color strokeFainterDark = Color.fromRGBO(255, 255, 255, 0.08);
// Other colors
const Color tabIconLight = Color.fromRGBO(0, 0, 0, 0.85);
const Color tabIconDark = Color.fromRGBO(255, 255, 255, 0.80);
// Fixed Colors

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
const blurBase = 96;
const blurMuted = 48;
const blurFaint = 24;
const blurBase = 96.0;
const blurMuted = 48.0;
const blurFaint = 24.0;
List<BoxShadow> shadowFloatLight = const [
BoxShadow(blurRadius: 10, color: Color.fromRGBO(0, 0, 0, 0.25)),

View file

@ -1,4 +1,5 @@
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:photos/ente_theme_data.dart';
import 'package:photos/theme/colors.dart';
import 'package:photos/theme/effects.dart';
import 'package:photos/theme/text_style.dart';
@ -34,3 +35,11 @@ EnteTheme darkTheme = EnteTheme(
shadowMenu: shadowMenuDark,
shadowButton: shadowButtonDark,
);
EnteColorScheme getEnteColorScheme(BuildContext context) {
return Theme.of(context).colorScheme.enteTheme.colorScheme;
}
EnteTextTheme getEnteTextTheme(BuildContext context) {
return Theme.of(context).colorScheme.enteTheme.textTheme;
}

View file

@ -1,7 +1,5 @@
// @dart=2.9
import 'dart:io';
import 'package:email_validator/email_validator.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@ -9,13 +7,9 @@ import 'package:flutter/services.dart';
import 'package:password_strength/password_strength.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/ente_theme_data.dart';
import 'package:photos/models/billing_plan.dart';
import 'package:photos/services/billing_service.dart';
import 'package:photos/services/user_service.dart';
import 'package:photos/ui/common/dynamic_fab.dart';
import 'package:photos/ui/common/loading_widget.dart';
import 'package:photos/ui/common/web_page.dart';
import 'package:photos/utils/data_util.dart';
import 'package:step_progress_indicator/step_progress_indicator.dart';
class EmailEntryPage extends StatefulWidget {
@ -502,134 +496,3 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
_passwordIsValid;
}
}
class PricingWidget extends StatelessWidget {
const PricingWidget({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return FutureBuilder<BillingPlans>(
future: BillingService.instance.getBillingPlans(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return _buildPlans(context, snapshot.data);
} else if (snapshot.hasError) {
return const Text("Oops, Something went wrong.");
}
return const EnteLoadingWidget();
},
);
}
Container _buildPlans(BuildContext context, BillingPlans plans) {
final planWidgets = <BillingPlanWidget>[];
for (final plan in plans.plans) {
final productID = Platform.isAndroid ? plan.androidID : plan.iosID;
if (productID != null && productID.isNotEmpty) {
planWidgets.add(BillingPlanWidget(plan));
}
}
final freePlan = plans.freePlan;
return Container(
height: 280,
color: Theme.of(context).cardColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
const Text(
"Pricing",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: planWidgets,
),
),
Text(
"We offer a free trial of " +
convertBytesToReadableFormat(freePlan.storage) +
" for " +
freePlan.duration.toString() +
" " +
freePlan.period,
),
GestureDetector(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Icon(
Icons.close,
size: 12,
color: Colors.white38,
),
Padding(padding: EdgeInsets.all(1)),
Text(
"Close",
style: TextStyle(
color: Colors.white38,
),
),
],
),
onTap: () => Navigator.pop(context),
)
],
),
);
}
}
class BillingPlanWidget extends StatelessWidget {
final BillingPlan plan;
const BillingPlanWidget(
this.plan, {
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(2.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
color: Colors.black.withOpacity(0.2),
child: Container(
padding: const EdgeInsets.fromLTRB(12, 20, 12, 20),
child: Column(
children: [
Text(
convertBytesToGBs(plan.storage, precision: 0).toString() +
" GB",
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
const Padding(
padding: EdgeInsets.all(4),
),
Text(
plan.price + " / " + plan.period,
style: const TextStyle(
fontSize: 12,
color: Colors.white70,
),
),
],
),
),
),
);
}
}

View file

@ -150,36 +150,33 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
Expanded(child: _getFolders()),
Column(
children: [
Hero(
tag: "select_folders",
child: Container(
width: double.infinity,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Theme.of(context).backgroundColor,
blurRadius: 24,
offset: const Offset(0, -8),
spreadRadius: 4,
)
],
),
padding: widget.isOnboarding
? const EdgeInsets.only(left: 20, right: 20)
: EdgeInsets.only(
top: 16,
left: 20,
right: 20,
bottom: Platform.isIOS ? 60 : 32,
),
child: OutlinedButton(
onPressed: _selectedDevicePathIDs.isEmpty
? null
: () async {
await updateFolderSettings();
},
child: Text(widget.buttonText),
),
Container(
width: double.infinity,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Theme.of(context).backgroundColor,
blurRadius: 24,
offset: const Offset(0, -8),
spreadRadius: 4,
)
],
),
padding: widget.isOnboarding
? const EdgeInsets.only(left: 20, right: 20)
: EdgeInsets.only(
top: 16,
left: 20,
right: 20,
bottom: Platform.isIOS ? 60 : 32,
),
child: OutlinedButton(
onPressed: _selectedDevicePathIDs.isEmpty
? null
: () async {
await updateFolderSettings();
},
child: Text(widget.buttonText),
),
),
widget.isOnboarding

View file

@ -3,6 +3,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/db/device_files_db.dart';
import 'package:photos/db/files_db.dart';
@ -40,6 +41,7 @@ class _DeviceFoldersGridViewWidgetState
@override
Widget build(BuildContext context) {
final logger = Logger((_DeviceFoldersGridViewWidgetState).toString());
final bool isMigrationDone =
LocalSyncService.instance.isDeviceFileMigrationDone();
return Padding(
@ -75,7 +77,8 @@ class _DeviceFoldersGridViewWidgetState
itemCount: snapshot.data.length,
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
logger.severe("failed to load device galler", snapshot.error);
return const Text("Failed to load albums");
} else {
return const EnteLoadingWidget();
}

View file

@ -1,33 +0,0 @@
// @dart=2.9
import 'package:flutter/material.dart';
import 'package:photos/utils/email_util.dart';
PopupMenuButton<dynamic> reportBugPopupMenu(BuildContext context) {
return PopupMenuButton(
itemBuilder: (context) {
final List<PopupMenuItem> items = [];
items.add(
PopupMenuItem(
value: 1,
child: Row(
children: const [
Text("Contact support"),
],
),
),
);
return items;
},
onSelected: (value) async {
if (value == 1) {
await sendLogs(
context,
"Contact support",
"support@ente.io",
postShare: () {},
);
}
},
);
}

View file

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
enum SizeVarient {
small(21),
medium(24),
large(28);
final double size;
const SizeVarient(this.size);
}
class BrandTitleWidget extends StatelessWidget {
final SizeVarient size;
const BrandTitleWidget({required this.size, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(
"ente",
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'Montserrat',
fontSize: SizeVarient.medium.size,
),
);
}
}

View file

@ -23,37 +23,55 @@ class ExpandableMenuItemWidget extends StatefulWidget {
class _ExpandableMenuItemWidgetState extends State<ExpandableMenuItemWidget> {
final expandableController = ExpandableController(initialExpanded: false);
@override
void initState() {
expandableController.addListener(() {
setState(() {});
});
super.initState();
}
@override
void dispose() {
expandableController.dispose();
expandableController.removeListener(() {});
super.dispose();
}
@override
Widget build(BuildContext context) {
final enteColorScheme = Theme.of(context).colorScheme.enteTheme.colorScheme;
return Container(
final backgroundColor =
MediaQuery.of(context).platformBrightness == Brightness.light
? enteColorScheme.backgroundElevated2
: enteColorScheme.backgroundElevated;
return AnimatedContainer(
curve: Curves.ease,
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
color: enteColorScheme.backgroundElevated2,
color: expandableController.value ? backgroundColor : null,
borderRadius: BorderRadius.circular(4),
),
child: ExpandablePanel(
header: MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: widget.title,
makeTextBold: true,
),
isHeaderOfExpansion: true,
leadingIcon: widget.leadingIcon,
trailingIcon: Icons.expand_more,
menuItemColor: enteColorScheme.fillFaint,
expandableController: expandableController,
),
collapsed: const SizedBox.shrink(),
expanded: widget.selectionOptionsWidget,
theme: getExpandableTheme(context),
child: ExpandableNotifier(
controller: expandableController,
child: ScrollOnExpand(
child: ExpandablePanel(
header: MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: widget.title,
makeTextBold: true,
),
isHeaderOfExpansion: true,
leadingIcon: widget.leadingIcon,
trailingIcon: Icons.expand_more,
menuItemColor: enteColorScheme.fillFaint,
expandableController: expandableController,
),
collapsed: const SizedBox.shrink(),
expanded: widget.selectionOptionsWidget,
theme: getExpandableTheme(context),
controller: expandableController,
),
),
),
);
}

View file

@ -0,0 +1,46 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/opened_settings_event.dart';
import 'package:photos/ui/viewer/search/search_widget.dart';
class HomeHeaderWidget extends StatefulWidget {
final Widget centerWidget;
const HomeHeaderWidget({required this.centerWidget, Key? key})
: super(key: key);
@override
State<HomeHeaderWidget> createState() => _HomeHeaderWidgetState();
}
class _HomeHeaderWidgetState extends State<HomeHeaderWidget> {
@override
Widget build(BuildContext context) {
final hasNotch = window.viewPadding.top > 65;
return Padding(
padding: EdgeInsets.fromLTRB(4, hasNotch ? 4 : 8, 4, 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
onPressed: () {
Scaffold.of(context).openDrawer();
Bus.instance.fire(OpenedSettingsEvent());
},
splashColor: Colors.transparent,
icon: const Icon(
Icons.menu_outlined,
),
),
AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
child: widget.centerWidget,
),
const SearchIconWidget(),
],
),
);
}
}

View file

@ -71,11 +71,22 @@ class _MenuItemWidgetState extends State<MenuItemWidget> {
Widget menuItemWidget(BuildContext context) {
final enteColorScheme = Theme.of(context).colorScheme.enteTheme.colorScheme;
return Container(
final borderRadius = Radius.circular(widget.borderRadius);
final isExpanded = widget.expandableController?.value;
final bottomBorderRadius = isExpanded != null && isExpanded
? const Radius.circular(0)
: borderRadius;
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(widget.borderRadius),
borderRadius: BorderRadius.only(
topLeft: borderRadius,
topRight: borderRadius,
bottomLeft: bottomBorderRadius,
bottomRight: bottomBorderRadius,
),
color: widget.menuItemColor,
),
child: Row(
@ -101,9 +112,21 @@ class _MenuItemWidgetState extends State<MenuItemWidget> {
),
widget.captionedTextWidget,
widget.expandableController != null
? _isExpanded()
? const SizedBox.shrink()
: Icon(widget.trailingIcon)
? AnimatedOpacity(
duration: const Duration(milliseconds: 100),
curve: Curves.easeInOut,
opacity: isExpanded! ? 0 : 1,
child: AnimatedSwitcher(
transitionBuilder: (child, animation) {
return ScaleTransition(scale: animation, child: child);
},
duration: const Duration(milliseconds: 200),
switchInCurve: Curves.easeOut,
child: isExpanded
? const SizedBox.shrink()
: Icon(widget.trailingIcon),
),
)
: widget.trailingIcon != null
? Icon(
widget.trailingIcon,
@ -116,8 +139,4 @@ class _MenuItemWidgetState extends State<MenuItemWidget> {
),
);
}
bool _isExpanded() {
return widget.expandableController!.value;
}
}

View file

@ -6,9 +6,11 @@ typedef OnChangedCallBack = void Function(bool);
class ToggleSwitchWidget extends StatefulWidget {
final bool value;
final OnChangedCallBack onChanged;
const ToggleSwitchWidget(
{required this.value, required this.onChanged, Key? key})
: super(key: key);
const ToggleSwitchWidget({
required this.value,
required this.onChanged,
Key? key,
}) : super(key: key);
@override
State<ToggleSwitchWidget> createState() => _ToggleSwitchWidgetState();

View file

@ -3,6 +3,8 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart' hide PageView;
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/opened_settings_event.dart';
/// This is copy-pasted from the Flutter framework with a support added for building
/// pages off screen using [Viewport.cacheExtents] and a [LayoutBuilder]
@ -58,6 +60,7 @@ class ExtentsPageView extends StatefulWidget {
this.onPageChanged,
List<Widget> children = const <Widget>[],
this.dragStartBehavior = DragStartBehavior.start,
this.openDrawer,
}) : controller = controller ?? _defaultPageController,
childrenDelegate = SliverChildListDelegate(children),
extents = children.length,
@ -90,6 +93,7 @@ class ExtentsPageView extends StatefulWidget {
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
this.dragStartBehavior = DragStartBehavior.start,
this.openDrawer,
}) : controller = controller ?? _defaultPageController,
childrenDelegate =
SliverChildBuilderDelegate(itemBuilder, childCount: itemCount),
@ -108,6 +112,7 @@ class ExtentsPageView extends StatefulWidget {
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
this.dragStartBehavior = DragStartBehavior.start,
this.openDrawer,
}) : controller = controller ?? _defaultPageController,
childrenDelegate = SliverChildBuilderDelegate(
itemBuilder,
@ -207,6 +212,7 @@ class ExtentsPageView extends StatefulWidget {
this.onPageChanged,
@required this.childrenDelegate,
this.dragStartBehavior = DragStartBehavior.start,
this.openDrawer,
}) : assert(childrenDelegate != null),
extents = 0,
controller = controller ?? _defaultPageController,
@ -272,6 +278,8 @@ class ExtentsPageView extends StatefulWidget {
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
final Function openDrawer; //nullable
@override
State<ExtentsPageView> createState() => _PageViewState();
}
@ -283,6 +291,20 @@ class _PageViewState extends State<ExtentsPageView> {
void initState() {
super.initState();
_lastReportedPage = widget.controller.initialPage;
widget.openDrawer != null
? widget.controller.addListener(() {
if (widget.controller.offset < -45) {
widget.openDrawer();
Bus.instance.fire(OpenedSettingsEvent());
}
})
: null;
}
@override
void dispose() {
widget.controller.dispose();
super.dispose();
}
AxisDirection _getDirection(BuildContext context) {

View file

@ -5,6 +5,7 @@ import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:move_to_background/move_to_background.dart';
@ -32,6 +33,10 @@ import 'package:photos/services/ignored_files_service.dart';
import 'package:photos/services/local_sync_service.dart';
import 'package:photos/services/update_service.dart';
import 'package:photos/services/user_service.dart';
import 'package:photos/states/user_details_state.dart';
import 'package:photos/theme/colors.dart';
import 'package:photos/theme/effects.dart';
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/backup_folder_selection_page.dart';
import 'package:photos/ui/collections_gallery_widget.dart';
import 'package:photos/ui/common/bottom_shadow.dart';
@ -74,7 +79,6 @@ class _HomeWidgetState extends State<HomeWidget> {
final _logger = Logger("HomeWidgetState");
final _selectedFiles = SelectedFiles();
// final _settingsButton = SettingsButton();
final PageController _pageController = PageController();
int _selectedTabIndex = 0;
Widget _headerWidgetWithSettingsButton;
@ -96,13 +100,10 @@ class _HomeWidgetState extends State<HomeWidget> {
@override
void initState() {
_logger.info("Building initstate");
_headerWidgetWithSettingsButton = Container(
margin: const EdgeInsets.only(top: 12),
child: Stack(
children: const [
_headerWidget,
],
),
_headerWidgetWithSettingsButton = Stack(
children: const [
_headerWidget,
],
);
_tabChangedEventSubscription =
Bus.instance.on<TabChangedEvent>().listen((event) {
@ -254,35 +255,60 @@ class _HomeWidgetState extends State<HomeWidget> {
@override
Widget build(BuildContext context) {
_logger.info("Building home_Widget with tab $_selectedTabIndex");
bool isSettingsOpen = false;
final enableDrawer = LocalSyncService.instance.hasCompletedFirstImport();
return WillPopScope(
child: Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(0),
child: Container(),
return UserDetailsStateWidget(
child: WillPopScope(
child: Scaffold(
drawerScrimColor: getEnteColorScheme(context).strokeFainter,
drawerEnableOpenDragGesture:
false, //using a hack instead of enabling this as enabling this will create other problems
drawer: enableDrawer
? ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 428),
child: Drawer(
width: double.infinity,
child: _settingsPage,
),
)
: null,
onDrawerChanged: (isOpened) => isSettingsOpen = isOpened,
body: SafeArea(
bottom: false,
child: Builder(
builder: (context) {
return _getBody(context);
},
),
),
resizeToAvoidBottomInset: false,
),
body: _getBody(),
resizeToAvoidBottomInset: false,
),
onWillPop: () async {
if (_selectedTabIndex == 0) {
if (Platform.isAndroid) {
MoveToBackground.moveTaskToBack();
return false;
onWillPop: () async {
if (_selectedTabIndex == 0) {
if (isSettingsOpen) {
Navigator.pop(context);
return false;
}
if (Platform.isAndroid) {
MoveToBackground.moveTaskToBack();
return false;
} else {
return true;
}
} else {
return true;
Bus.instance
.fire(TabChangedEvent(0, TabChangedEventSource.backButton));
return false;
}
} else {
Bus.instance
.fire(TabChangedEvent(0, TabChangedEventSource.backButton));
return false;
}
},
},
),
);
}
Widget _getBody() {
Widget _getBody(BuildContext context) {
if (!Configuration.instance.hasConfiguredAccount()) {
_closeDrawerIfOpen(context);
return const LandingPageWidget();
}
if (!LocalSyncService.instance.hasGrantedPermissions()) {
@ -295,6 +321,7 @@ class _HomeWidgetState extends State<HomeWidget> {
ReceiveSharingIntent.reset();
return CreateCollectionPage(null, _sharedFiles);
}
final isBottomInsetPresent = MediaQuery.of(context).viewPadding.bottom != 0;
final bool showBackupFolderHook =
!Configuration.instance.hasSelectedAnyBackupFolder() &&
@ -302,24 +329,29 @@ class _HomeWidgetState extends State<HomeWidget> {
CollectionsService.instance.getActiveCollections().isEmpty;
return Stack(
children: [
ExtentsPageView(
onPageChanged: (page) {
Bus.instance.fire(
TabChangedEvent(
page,
TabChangedEventSource.pageView,
),
Builder(
builder: (context) {
return ExtentsPageView(
onPageChanged: (page) {
Bus.instance.fire(
TabChangedEvent(
page,
TabChangedEventSource.pageView,
),
);
},
controller: _pageController,
openDrawer: Scaffold.of(context).openDrawer,
physics: const BouncingScrollPhysics(),
children: [
showBackupFolderHook
? _getBackupFolderSelectionHook()
: _getMainGalleryWidget(),
_deviceFolderGalleryWidget,
_sharedCollectionGallery,
],
);
},
controller: _pageController,
children: [
showBackupFolderHook
? _getBackupFolderSelectionHook()
: _getMainGalleryWidget(),
_deviceFolderGalleryWidget,
_sharedCollectionGallery,
_settingsPage,
],
),
const Align(
alignment: Alignment.bottomCenter,
@ -327,8 +359,8 @@ class _HomeWidgetState extends State<HomeWidget> {
),
Align(
alignment: Alignment.bottomCenter,
child: SafeArea(
minimum: const EdgeInsets.only(bottom: 8),
child: Padding(
padding: EdgeInsets.only(bottom: isBottomInsetPresent ? 32 : 8),
child: HomeBottomNavigationBar(
_selectedFiles,
selectedTabIndex: _selectedTabIndex,
@ -343,6 +375,14 @@ class _HomeWidgetState extends State<HomeWidget> {
);
}
void _closeDrawerIfOpen(BuildContext context) {
Scaffold.of(context).isDrawerOpen
? SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
Scaffold.of(context).closeDrawer();
})
: null;
}
Future<bool> _initDeepLinks() async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
@ -472,30 +512,27 @@ class _HomeWidgetState extends State<HomeWidget> {
.copyWith(fontFamily: 'Inter-Medium', fontSize: 16),
),
Center(
child: Hero(
tag: "select_folders",
child: Material(
type: MaterialType.transparency,
child: Container(
width: double.infinity,
height: 64,
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: GradientButton(
onTap: () async {
if (LocalSyncService.instance
.hasGrantedLimitedPermissions()) {
PhotoManager.presentLimited();
} else {
routeToPage(
context,
const BackupFolderSelectionPage(
buttonText: "Start backup",
),
);
}
},
text: "Start backup",
),
child: Material(
type: MaterialType.transparency,
child: Container(
width: double.infinity,
height: 64,
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: GradientButton(
onTap: () async {
if (LocalSyncService.instance
.hasGrantedLimitedPermissions()) {
PhotoManager.presentLimited();
} else {
routeToPage(
context,
const BackupFolderSelectionPage(
buttonText: "Start backup",
),
);
}
},
text: "Start backup",
),
),
),
@ -602,10 +639,16 @@ class _HomeBottomNavigationBarState extends State<HomeBottomNavigationBar> {
@override
Widget build(BuildContext context) {
final bool filesAreSelected = widget.selectedFiles.files.isNotEmpty;
final enteColorScheme = getEnteColorScheme(context);
final navBarBlur =
MediaQuery.of(context).platformBrightness == Brightness.light
? blurBase
: blurMuted;
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
height: filesAreSelected ? 0 : 52,
height: filesAreSelected ? 0 : 56,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 100),
opacity: filesAreSelected ? 0.0 : 1.0,
@ -619,42 +662,40 @@ class _HomeBottomNavigationBarState extends State<HomeBottomNavigationBar> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(36),
borderRadius: BorderRadius.circular(32),
child: Container(
alignment: Alignment.bottomCenter,
height: 52,
width: 240,
height: 48,
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
filter: ImageFilter.blur(
sigmaX: navBarBlur,
sigmaY: navBarBlur,
),
child: GNav(
curve: Curves.easeOutExpo,
backgroundColor: Theme.of(context)
.colorScheme
.gNavBackgroundColor,
backgroundColor:
getEnteColorScheme(context).fillMuted,
mainAxisAlignment: MainAxisAlignment.center,
rippleColor: Colors.white.withOpacity(0.1),
activeColor: Theme.of(context)
.colorScheme
.gNavBarActiveColor,
iconSize: 24,
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
padding: const EdgeInsets.fromLTRB(16, 6, 16, 6),
duration: const Duration(milliseconds: 200),
gap: 0,
tabBorderRadius: 24,
tabBorderRadius: 32,
tabBackgroundColor: Theme.of(context)
.colorScheme
.gNavBarActiveColor,
haptic: false,
tabs: [
GButton(
margin: const EdgeInsets.fromLTRB(6, 6, 0, 6),
icon: Icons.home,
iconColor:
Theme.of(context).colorScheme.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
margin: const EdgeInsets.fromLTRB(8, 6, 10, 6),
icon: Icons.home_rounded,
iconColor: enteColorScheme.tabIcon,
iconActiveColor: strokeBaseLight,
text: '',
onPressed: () {
_onTabChange(
@ -663,13 +704,10 @@ class _HomeBottomNavigationBarState extends State<HomeBottomNavigationBar> {
},
),
GButton(
margin: const EdgeInsets.fromLTRB(0, 6, 0, 6),
icon: Icons.photo_library,
iconColor:
Theme.of(context).colorScheme.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
margin: const EdgeInsets.fromLTRB(10, 6, 10, 6),
icon: Icons.collections_rounded,
iconColor: enteColorScheme.tabIcon,
iconActiveColor: strokeBaseLight,
text: '',
onPressed: () {
_onTabChange(
@ -678,13 +716,10 @@ class _HomeBottomNavigationBarState extends State<HomeBottomNavigationBar> {
},
),
GButton(
margin: const EdgeInsets.fromLTRB(0, 6, 0, 6),
icon: Icons.folder_shared,
iconColor:
Theme.of(context).colorScheme.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
margin: const EdgeInsets.fromLTRB(10, 6, 8, 6),
icon: Icons.people_outlined,
iconColor: enteColorScheme.tabIcon,
iconActiveColor: strokeBaseLight,
text: '',
onPressed: () {
_onTabChange(
@ -692,21 +727,6 @@ class _HomeBottomNavigationBarState extends State<HomeBottomNavigationBar> {
); // To take care of occasional missing events
},
),
GButton(
margin: const EdgeInsets.fromLTRB(0, 6, 6, 6),
icon: Icons.person,
iconColor:
Theme.of(context).colorScheme.gNavIconColor,
iconActiveColor: Theme.of(context)
.colorScheme
.gNavActiveIconColor,
text: '',
onPressed: () {
_onTabChange(
3,
); // To take care of occasional missing events
},
)
],
selectedIndex: currentTabIndex,
onTabChange: _onTabChange,

View file

@ -289,7 +289,9 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
RichText(
text: TextSpan(
text: "Manage family",
style: Theme.of(context).textTheme.overline,
style: Theme.of(context).textTheme.bodyMedium.copyWith(
decoration: TextDecoration.underline,
),
),
textAlign: TextAlign.center,
),

View file

@ -132,7 +132,9 @@ class SubFaqWidget extends StatelessWidget {
child: RichText(
text: TextSpan(
text: "Questions?",
style: Theme.of(context).textTheme.overline,
style: Theme.of(context).textTheme.bodyMedium.copyWith(
decoration: TextDecoration.underline,
),
),
),
),

View file

@ -60,9 +60,8 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
}
void _setupPurchaseUpdateStreamListener() {
_purchaseUpdateSubscription = InAppPurchaseConnection
.instance.purchaseUpdatedStream
.listen((purchases) async {
_purchaseUpdateSubscription =
InAppPurchase.instance.purchaseStream.listen((purchases) async {
if (!_dialog.isShowing()) {
await _dialog.show();
}
@ -74,7 +73,7 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
purchase.productID,
purchase.verificationData.serverVerificationData,
);
await InAppPurchaseConnection.instance.completePurchase(purchase);
await InAppPurchase.instance.completePurchase(purchase);
String text = "Thank you for subscribing!";
if (!widget.isOnboarding) {
final isUpgrade = _hasActiveSubscription &&
@ -121,7 +120,7 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
return;
}
} else if (Platform.isIOS && purchase.pendingCompletePurchase) {
await InAppPurchaseConnection.instance.completePurchase(purchase);
await InAppPurchase.instance.completePurchase(purchase);
await _dialog.hide();
} else if (purchase.status == PurchaseStatus.error) {
await _dialog.hide();
@ -172,7 +171,9 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
}).toList();
_freePlan = billingPlans.freePlan;
_hasLoadedData = true;
setState(() {});
if (mounted) {
setState(() {});
}
});
}
@ -294,7 +295,9 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
RichText(
text: TextSpan(
text: "Manage family",
style: Theme.of(context).textTheme.overline,
style: Theme.of(context).textTheme.bodyMedium.copyWith(
decoration: TextDecoration.underline,
),
),
textAlign: TextAlign.center,
),
@ -389,14 +392,13 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
showErrorDialog(
context,
"Sorry",
"you cannot downgrade to this plan",
"You cannot downgrade to this plan",
);
return;
}
await _dialog.show();
final ProductDetailsResponse response =
await InAppPurchaseConnection.instance
.queryProductDetails({productID});
await InAppPurchase.instance.queryProductDetails({productID});
if (response.notFoundIDs.isNotEmpty) {
_logger.severe(
"Could not find products: " + response.notFoundIDs.toString(),
@ -410,34 +412,15 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
_currentSubscription.productID != freeProductID &&
_currentSubscription.productID != plan.androidID;
if (isCrossGradingOnAndroid) {
final existingProductDetailsResponse =
await InAppPurchaseConnection.instance
.queryProductDetails({_currentSubscription.productID});
if (existingProductDetailsResponse.notFoundIDs.isNotEmpty) {
_logger.severe(
"Could not find existing products: " +
response.notFoundIDs.toString(),
);
await _dialog.hide();
showGenericErrorDialog(context);
return;
}
final subscriptionChangeParam = ChangeSubscriptionParam(
oldPurchaseDetails: PurchaseDetails(
purchaseID: null,
productID: _currentSubscription.productID,
verificationData: null,
transactionDate: null,
),
);
await InAppPurchaseConnection.instance.buyNonConsumable(
purchaseParam: PurchaseParam(
productDetails: response.productDetails[0],
changeSubscriptionParam: subscriptionChangeParam,
),
await _dialog.hide();
showErrorDialog(
context,
"Could not update subscription",
"Please contact support@ente.io and we will be happy to help!",
);
return;
} else {
await InAppPurchaseConnection.instance.buyNonConsumable(
await InAppPurchase.instance.buyNonConsumable(
purchaseParam: PurchaseParam(
productDetails: response.productDetails[0],
),

View file

@ -113,6 +113,7 @@ class AccountSectionWidget extends StatelessWidget {
}
},
),
sectionOptionSpacing,
],
);
}

View file

@ -10,5 +10,6 @@ ExpandableThemeData getExpandableTheme(BuildContext context) {
useInkWell: false,
tapBodyToCollapse: true,
tapBodyToExpand: true,
animationDuration: Duration(milliseconds: 400),
);
}

View file

@ -1,45 +1,26 @@
// @dart=2.9
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/tab_changed_event.dart';
import 'package:photos/events/user_details_changed_event.dart';
import 'package:logging/logging.dart';
import 'package:photos/models/user_details.dart';
import 'package:photos/services/user_service.dart';
import 'package:photos/states/user_details_state.dart';
import 'package:photos/ui/common/loading_widget.dart';
// ignore: import_of_legacy_library_into_null_safe
import 'package:photos/ui/payment/subscription.dart';
import 'package:photos/utils/data_util.dart';
class DetailsSectionWidget extends StatefulWidget {
const DetailsSectionWidget({Key key}) : super(key: key);
const DetailsSectionWidget({Key? key}) : super(key: key);
@override
State<DetailsSectionWidget> createState() => _DetailsSectionWidgetState();
}
class _DetailsSectionWidgetState extends State<DetailsSectionWidget> {
UserDetails _userDetails;
StreamSubscription<UserDetailsChangedEvent> _userDetailsChangedEvent;
StreamSubscription<TabChangedEvent> _tabChangedEventSubscription;
Image _background;
late Image _background;
final _logger = Logger((_DetailsSectionWidgetState).toString());
@override
void initState() {
super.initState();
_fetchUserDetails();
_userDetailsChangedEvent =
Bus.instance.on<UserDetailsChangedEvent>().listen((event) {
_fetchUserDetails();
});
_tabChangedEventSubscription =
Bus.instance.on<TabChangedEvent>().listen((event) {
if (event.selectedIndex == 3) {
_fetchUserDetails();
}
});
_background = const Image(
image: AssetImage("assets/storage_card_background.png"),
fit: BoxFit.fill,
@ -54,41 +35,38 @@ class _DetailsSectionWidgetState extends State<DetailsSectionWidget> {
precacheImage(_background.image, context);
}
void _fetchUserDetails() {
UserService.instance.getUserDetailsV2(memoryCount: true).then((details) {
if (mounted) {
setState(() {
_userDetails = details;
});
}
});
}
@override
void dispose() {
_userDetailsChangedEvent.cancel();
_tabChangedEventSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return getSubscriptionPage();
},
),
);
},
child: getContainer(),
);
final inheritedUserDetails = InheritedUserDetails.of(context);
if (inheritedUserDetails == null) {
_logger.severe(
(InheritedUserDetails).toString() +
' not found before ' +
(_DetailsSectionWidgetState).toString() +
' on tree',
);
throw Error();
} else {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return getSubscriptionPage();
},
),
);
},
child: containerForUserDetails(inheritedUserDetails),
);
}
}
Widget getContainer() {
Widget containerForUserDetails(
InheritedUserDetails inheritedUserDetails,
) {
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 428, maxHeight: 175),
child: Stack(
@ -101,162 +79,19 @@ class _DetailsSectionWidgetState extends State<DetailsSectionWidget> {
child: _background,
),
),
_userDetails == null
? const EnteLoadingWidget()
: Padding(
padding: const EdgeInsets.only(
top: 20,
bottom: 20,
left: 16,
right: 16,
),
child: Container(
color: Colors.transparent,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Align(
alignment: Alignment.topLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Storage",
style: Theme.of(context)
.textTheme
.subtitle2
.copyWith(
color: Colors.white.withOpacity(0.7),
),
),
Text(
"${convertBytesToReadableFormat(_userDetails.getFreeStorage())} of ${convertBytesToReadableFormat(_userDetails.getTotalStorage())} free",
style: Theme.of(context)
.textTheme
.headline5
.copyWith(color: Colors.white),
),
],
),
),
Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Stack(
children: <Widget>[
Container(
color: Colors.white.withOpacity(0.2),
width: MediaQuery.of(context).size.width,
height: 4,
),
Container(
color: Colors.white.withOpacity(0.75),
width: MediaQuery.of(context).size.width *
((_userDetails
.getFamilyOrPersonalUsage()) /
_userDetails.getTotalStorage()),
height: 4,
),
Container(
color: Colors.white,
width: MediaQuery.of(context).size.width *
(_userDetails.usage /
_userDetails.getTotalStorage()),
height: 4,
),
],
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_userDetails.isPartOfFamily()
? Row(
children: [
Container(
width: 8.71,
height: 8.99,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
),
const Padding(
padding: EdgeInsets.only(right: 4),
),
Text(
"You",
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color: Colors.white,
fontSize: 12,
),
),
const Padding(
padding: EdgeInsets.only(right: 12),
),
Container(
width: 8.71,
height: 8.99,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white
.withOpacity(0.75),
),
),
const Padding(
padding: EdgeInsets.only(right: 4),
),
Text(
"Family",
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color: Colors.white,
fontSize: 12,
),
),
],
)
: Text(
"${convertBytesToReadableFormat(_userDetails.getFamilyOrPersonalUsage())} used",
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color: Colors.white,
fontSize: 12,
),
),
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Text(
"${NumberFormat().format(_userDetails.fileCount)} Memories",
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color: Colors.white,
fontSize: 12,
),
),
)
],
),
],
)
],
),
),
),
FutureBuilder(
future: inheritedUserDetails.userDetails,
builder: (context, snapshot) {
if (snapshot.hasData) {
return userDetails(snapshot.data as UserDetails);
}
if (snapshot.hasError) {
_logger.severe('failed to load user details', snapshot.error);
return const EnteLoadingWidget();
}
return const EnteLoadingWidget();
},
),
const Align(
alignment: Alignment.centerRight,
child: Icon(
@ -269,4 +104,140 @@ class _DetailsSectionWidgetState extends State<DetailsSectionWidget> {
),
);
}
Widget userDetails(UserDetails userDetails) {
return Padding(
padding: const EdgeInsets.only(
top: 20,
bottom: 20,
left: 16,
right: 16,
),
child: Container(
color: Colors.transparent,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Align(
alignment: Alignment.topLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Storage",
style: Theme.of(context).textTheme.subtitle2!.copyWith(
color: Colors.white.withOpacity(0.7),
),
),
Text(
"${convertBytesToReadableFormat(userDetails.getFreeStorage())} of ${convertBytesToReadableFormat(userDetails.getTotalStorage())} free",
style: Theme.of(context)
.textTheme
.headline5!
.copyWith(color: Colors.white),
),
],
),
),
Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Stack(
children: <Widget>[
Container(
color: Colors.white.withOpacity(0.2),
width: MediaQuery.of(context).size.width,
height: 4,
),
Container(
color: Colors.white.withOpacity(0.75),
width: MediaQuery.of(context).size.width *
((userDetails.getFamilyOrPersonalUsage()) /
userDetails.getTotalStorage()),
height: 4,
),
Container(
color: Colors.white,
width: MediaQuery.of(context).size.width *
(userDetails.usage / userDetails.getTotalStorage()),
height: 4,
),
],
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
userDetails.isPartOfFamily()
? Row(
children: [
Container(
width: 8.71,
height: 8.99,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
),
const Padding(
padding: EdgeInsets.only(right: 4),
),
Text(
"You",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(
color: Colors.white,
fontSize: 12,
),
),
const Padding(
padding: EdgeInsets.only(right: 12),
),
Container(
width: 8.71,
height: 8.99,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.75),
),
),
const Padding(
padding: EdgeInsets.only(right: 4),
),
Text(
"Family",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(
color: Colors.white,
fontSize: 12,
),
),
],
)
: Text(
"${convertBytesToReadableFormat(userDetails.getFamilyOrPersonalUsage())} used",
style:
Theme.of(context).textTheme.bodyText1!.copyWith(
color: Colors.white,
fontSize: 12,
),
),
],
),
],
)
],
),
),
);
}
}

View file

@ -129,7 +129,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
captionedTextWidget: const CaptionedTextWidget(
title: "Hide from recents",
),
trailingSwitch: Switch.adaptive(
trailingSwitch: ToggleSwitchWidget(
value: _config.shouldHideFromRecents(),
onChanged: (value) async {
if (value) {

View file

@ -1,36 +0,0 @@
// @dart=2.9
import 'package:flutter/material.dart';
class SettingsSectionTitle extends StatelessWidget {
final String title;
final Color color;
const SettingsSectionTitle(
this.title, {
Key key,
this.color,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
const Padding(padding: EdgeInsets.all(4)),
Align(
alignment: Alignment.centerLeft,
child: Text(
title,
style: color != null
? Theme.of(context)
.textTheme
.headline6
.merge(TextStyle(color: color))
: Theme.of(context).textTheme.headline6,
),
),
const Padding(padding: EdgeInsets.all(4)),
],
);
}
}

View file

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:logging/logging.dart';
import 'package:photos/models/user_details.dart';
import 'package:photos/states/user_details_state.dart';
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/common/loading_widget.dart';
class SettingsTitleBarWidget extends StatelessWidget {
const SettingsTitleBarWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final logger = Logger((SettingsTitleBarWidget).toString());
return Container(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 20, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.keyboard_double_arrow_left_outlined),
),
FutureBuilder(
future: InheritedUserDetails.of(context)?.userDetails,
builder: (context, snapshot) {
if (InheritedUserDetails.of(context) == null) {
logger.severe(
(InheritedUserDetails).toString() +
' not found before ' +
(SettingsTitleBarWidget).toString() +
' on tree',
);
throw Error();
}
if (snapshot.hasData) {
final userDetails = snapshot.data as UserDetails;
return Text(
"${NumberFormat().format(userDetails.fileCount)} memories",
style: getEnteTextTheme(context).largeBold,
);
}
if (snapshot.hasError) {
logger.severe('failed to load user details');
return const EnteLoadingWidget();
} else {
return const EnteLoadingWidget();
}
},
)
],
),
),
);
}
}

View file

@ -53,8 +53,7 @@ class SupportSectionWidget extends StatelessWidget {
builder: (BuildContext context) {
final endpoint = Configuration.instance.getHttpEndpoint() +
"/users/roadmap";
final isLoggedIn = Configuration.instance.getToken() != null;
final url = isLoggedIn
final url = Configuration.instance.isLoggedIn()
? endpoint + "?token=" + Configuration.instance.getToken()
: roadmapURL;
return WebPage("Roadmap", url);

View file

@ -5,8 +5,9 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/ente_theme_data.dart';
import 'package:photos/services/feature_flag_service.dart';
import 'package:photos/theme/colors.dart';
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/settings/about_section_widget.dart';
import 'package:photos/ui/settings/account_section_widget.dart';
import 'package:photos/ui/settings/app_version_widget.dart';
@ -15,6 +16,7 @@ import 'package:photos/ui/settings/danger_section_widget.dart';
import 'package:photos/ui/settings/debug_section_widget.dart';
import 'package:photos/ui/settings/details_section_widget.dart';
import 'package:photos/ui/settings/security_section_widget.dart';
import 'package:photos/ui/settings/settings_title_bar_widget.dart';
import 'package:photos/ui/settings/social_section_widget.dart';
import 'package:photos/ui/settings/support_section_widget.dart';
import 'package:photos/ui/settings/theme_switch_widget.dart';
@ -25,24 +27,22 @@ class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final enteColorScheme = getEnteColorScheme(context);
return Scaffold(
body: Container(
color: Theme.of(context)
.colorScheme
.enteTheme
.colorScheme
.backgroundElevated,
child: _getBody(context),
color: enteColorScheme.backdropBase,
child: _getBody(context, enteColorScheme),
),
);
}
Widget _getBody(BuildContext context) {
final hasLoggedIn = Configuration.instance.getToken() != null;
Widget _getBody(BuildContext context, EnteColorScheme colorScheme) {
final hasLoggedIn = Configuration.instance.isLoggedIn();
final enteTextTheme = getEnteTextTheme(context);
final List<Widget> contents = [];
contents.add(
Container(
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Align(
alignment: Alignment.centerLeft,
child: AnimatedBuilder(
@ -51,10 +51,10 @@ class SettingsPage extends StatelessWidget {
builder: (BuildContext context, Widget child) {
return Text(
emailNotifier.value,
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(overflow: TextOverflow.ellipsis),
style: enteTextTheme.body.copyWith(
color: colorScheme.textMuted,
overflow: TextOverflow.ellipsis,
),
);
},
),
@ -110,16 +110,20 @@ class SettingsPage extends StatelessWidget {
),
);
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 428),
child: Column(
children: contents,
return SafeArea(
bottom: false,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SettingsTitleBarWidget(),
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
child: Column(
children: contents,
),
),
),
],
),
),
);

View file

@ -10,9 +10,10 @@ import 'package:photos/events/sync_status_update_event.dart';
import 'package:photos/services/sync_service.dart';
import 'package:photos/services/user_remote_flag_service.dart';
import 'package:photos/ui/account/verify_recovery_page.dart';
import 'package:photos/ui/components/brand_title_widget.dart';
import 'package:photos/ui/components/home_header_widget.dart';
import 'package:photos/ui/components/notification_warning_widget.dart';
import 'package:photos/ui/header_error_widget.dart';
import 'package:photos/ui/viewer/search/search_widget.dart';
import 'package:photos/utils/navigation_util.dart';
const double kContainerHeight = 36;
@ -29,16 +30,19 @@ class _StatusBarWidgetState extends State<StatusBarWidget> {
StreamSubscription<NotificationEvent> _notificationSubscription;
bool _showStatus = false;
bool _showErrorBanner = false;
Error _syncError;
@override
void initState() {
_subscription = Bus.instance.on<SyncStatusUpdate>().listen((event) {
if (event.status == SyncStatus.error) {
setState(() {
_syncError = event.error;
_showErrorBanner = true;
});
} else {
setState(() {
_syncError = null;
_showErrorBanner = false;
});
}
@ -77,38 +81,23 @@ class _StatusBarWidgetState extends State<StatusBarWidget> {
Widget build(BuildContext context) {
return Column(
children: [
Stack(
children: [
AnimatedOpacity(
opacity: _showStatus
? _showErrorBanner
? 1
: 0
: 1,
duration: const Duration(milliseconds: 1000),
child: const BrandingWidget(),
),
AnimatedOpacity(
opacity: _showStatus ? 1 : 0,
duration: const Duration(milliseconds: 1000),
child: const SyncStatusWidget(),
),
Positioned(
right: 0,
top: 0,
child: Container(
color: Theme.of(context).colorScheme.defaultBackgroundColor,
height: kContainerHeight,
child: const SearchIconWidget(),
),
),
],
HomeHeaderWidget(
centerWidget: _showStatus
? _showErrorBanner
? const BrandTitleWidget(size: SizeVarient.medium)
: const SyncStatusWidget()
: const BrandTitleWidget(size: SizeVarient.medium),
),
AnimatedOpacity(
opacity: _showStatus ? 1 : 0,
duration: const Duration(milliseconds: 1000),
child: const Divider(),
opacity: _showErrorBanner ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: const Divider(
height: 8,
),
),
_showErrorBanner
? HeaderErrorWidget(error: _syncError)
: const SizedBox.shrink(),
UserRemoteFlagService.instance.shouldShowRecoveryVerification()
? NotificationWarningWidget(
warningIcon: Icons.gpp_maybe,
@ -165,15 +154,12 @@ class _SyncStatusWidgetState extends State<SyncStatusWidget> {
_event.status == SyncStatus.completedFirstGalleryImport) &&
(DateTime.now().microsecondsSinceEpoch - _event.timestamp >
kSleepDuration.inMicroseconds);
if (_event == null || isNotOutdatedEvent) {
if (_event == null ||
isNotOutdatedEvent ||
//sync error cases are handled in StatusBarWidget
_event.status == SyncStatus.error) {
return const SizedBox.shrink();
}
if (_event.status == SyncStatus.error) {
return Padding(
padding: const EdgeInsets.only(top: kContainerHeight + 8),
child: HeaderErrorWidget(error: _event.error),
);
}
if (_event.status == SyncStatus.completedBackup) {
return const SyncStatusCompletedWidget();
}
@ -195,7 +181,6 @@ class RefreshIndicatorWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
height: kContainerHeight,
width: double.infinity,
alignment: Alignment.center,
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),

View file

@ -1,93 +0,0 @@
// @dart=2.9
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:photos/models/file.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/file_util.dart';
import 'package:photos/utils/toast_util.dart';
import 'package:wallpaper_manager_flutter/wallpaper_manager_flutter.dart';
class SetWallpaperDialog extends StatefulWidget {
final File file;
const SetWallpaperDialog(this.file, {Key key}) : super(key: key);
@override
State<SetWallpaperDialog> createState() => _SetWallpaperDialogState();
}
class _SetWallpaperDialogState extends State<SetWallpaperDialog> {
int _lockscreenValue = WallpaperManagerFlutter.HOME_SCREEN;
@override
Widget build(BuildContext context) {
final alert = AlertDialog(
title: const Text("Set wallpaper"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile(
title: const Text("Homescreen"),
value: WallpaperManagerFlutter.HOME_SCREEN,
groupValue: _lockscreenValue,
onChanged: (v) {
setState(() {
_lockscreenValue = v;
});
},
),
RadioListTile(
title: const Text("Lockscreen"),
value: WallpaperManagerFlutter.LOCK_SCREEN,
groupValue: _lockscreenValue,
onChanged: (v) {
setState(() {
_lockscreenValue = v;
});
},
),
RadioListTile(
title: const Text("Both"),
value: WallpaperManagerFlutter.BOTH_SCREENS,
groupValue: _lockscreenValue,
onChanged: (v) {
setState(() {
_lockscreenValue = v;
});
},
),
],
),
actions: [
TextButton(
child: const Text(
"Ok",
style: TextStyle(
color: Colors.white,
),
),
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop('dialog');
final dialog = createProgressDialog(context, "Setting wallpaper");
await dialog.show();
try {
await WallpaperManagerFlutter().setwallpaperfromFile(
await getFile(widget.file),
_lockscreenValue,
);
await dialog.hide();
showToast(context, "Wallpaper set successfully");
} catch (e, s) {
await dialog.hide();
Logger("SetWallpaperDialog").severe(e, s);
showToast(context, "Something went wrong");
return;
}
},
),
],
);
return alert;
}
}

View file

@ -7,6 +7,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:like_button/like_button.dart';
import 'package:logging/logging.dart';
import 'package:media_extension/media_extension.dart';
import 'package:path/path.dart' as file_path;
import 'package:photo_manager/photo_manager.dart';
import 'package:photos/core/event_bus.dart';
@ -153,6 +154,27 @@ class FadingAppBarState extends State<FadingAppBar> {
),
);
}
if ((widget.file.fileType == FileType.image ||
widget.file.fileType == FileType.livePhoto) &&
Platform.isAndroid) {
items.add(
PopupMenuItem(
value: 3,
child: Row(
children: [
Icon(
Icons.wallpaper_outlined,
color: Theme.of(context).iconTheme.color,
),
const Padding(
padding: EdgeInsets.all(8),
),
const Text("Use as"),
],
),
),
);
}
return items;
},
onSelected: (value) {
@ -160,6 +182,8 @@ class FadingAppBarState extends State<FadingAppBar> {
_download(widget.file);
} else if (value == 2) {
_showDeleteSheet(widget.file);
} else if (value == 3) {
_setAs(widget.file);
}
},
),
@ -344,4 +368,22 @@ class FadingAppBarState extends State<FadingAppBar> {
showToast(context, "File saved to gallery");
}
}
Future<void> _setAs(File file) async {
final dialog = createProgressDialog(context, "Please wait...");
await dialog.show();
try {
final io.File fileToSave = await getFile(file);
var m = MediaExtension();
final bool result = await m.setAs("file://${fileToSave.path}", "image/*");
if (result == false) {
showShortToast(context, "Something went wrong");
}
dialog.hide();
} catch (e) {
dialog.hide();
_logger.severe("Failed to use as", e);
showGenericErrorDialog(context);
}
}
}

View file

@ -49,6 +49,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
Function() _selectedFilesListener;
String _appBarTitle;
final GlobalKey shareButtonKey = GlobalKey();
@override
void initState() {
_selectedFilesListener = () {
@ -72,26 +73,28 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
@override
Widget build(BuildContext context) {
// if (widget.selectedFiles.files.isEmpty) {
return AppBar(
backgroundColor:
widget.type == GalleryType.homepage ? const Color(0x00000000) : null,
elevation: 0,
centerTitle: false,
title: widget.type == GalleryType.homepage
? const SizedBox.shrink()
: TextButton(
child: Text(
_appBarTitle,
style: Theme.of(context)
.textTheme
.headline5
.copyWith(fontSize: 16),
),
onPressed: () => _renameAlbum(context),
),
actions: _getDefaultActions(context),
);
return widget.type == GalleryType.homepage
? const SizedBox.shrink()
: AppBar(
backgroundColor: widget.type == GalleryType.homepage
? const Color(0x00000000)
: null,
elevation: 0,
centerTitle: false,
title: widget.type == GalleryType.homepage
? const SizedBox.shrink()
: TextButton(
child: Text(
_appBarTitle,
style: Theme.of(context)
.textTheme
.headline5
.copyWith(fontSize: 16),
),
onPressed: () => _renameAlbum(context),
),
actions: _getDefaultActions(context),
);
}
Future<dynamic> _renameAlbum(BuildContext context) async {
@ -125,6 +128,33 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
}
}
Future<dynamic> _leaveAlbum(BuildContext context) async {
final DialogUserChoice result = await showChoiceDialog(
context,
"Leave shared album?",
"You will leave the album, and it will stop being visible to you.",
firstAction: "Cancel",
secondAction: "Yes, Leave",
secondActionColor:
Theme.of(context).colorScheme.enteTheme.colorScheme.warning700,
);
if (result != DialogUserChoice.secondChoice) {
return;
}
final dialog = createProgressDialog(context, "Leaving album...");
await dialog.show();
try {
await CollectionsService.instance.leaveAlbum(widget.collection);
await dialog.hide();
if (mounted) {
Navigator.of(context).pop();
}
} catch (e) {
await dialog.hide();
showGenericErrorDialog(context);
}
}
List<Widget> _getDefaultActions(BuildContext context) {
final List<Widget> actions = <Widget>[];
if (Configuration.instance.hasConfiguredAccount() &&
@ -158,78 +188,101 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
),
);
}
final List<PopupMenuItem> items = [];
if (widget.type == GalleryType.ownedCollection) {
actions.add(
PopupMenuButton(
itemBuilder: (context) {
final List<PopupMenuItem> items = [];
if (widget.collection.type != CollectionType.favorites) {
items.add(
PopupMenuItem(
value: 1,
child: Row(
children: const [
Icon(Icons.edit),
Padding(
padding: EdgeInsets.all(8),
),
Text("Rename album"),
],
),
),
);
}
final bool isArchived = widget.collection.isArchived();
items.add(
PopupMenuItem(
value: 2,
child: Row(
children: [
Icon(isArchived ? Icons.visibility : Icons.visibility_off),
const Padding(
padding: EdgeInsets.all(8),
),
Text(isArchived ? "Unhide album" : "Hide album"),
],
if (widget.collection.type != CollectionType.favorites) {
items.add(
PopupMenuItem(
value: 1,
child: Row(
children: const [
Icon(Icons.edit),
Padding(
padding: EdgeInsets.all(8),
),
Text("Rename album"),
],
),
),
);
}
final bool isArchived = widget.collection.isArchived();
items.add(
PopupMenuItem(
value: 2,
child: Row(
children: [
Icon(isArchived ? Icons.visibility : Icons.visibility_off),
const Padding(
padding: EdgeInsets.all(8),
),
);
if (widget.collection.type != CollectionType.favorites) {
items.add(
PopupMenuItem(
value: 3,
child: Row(
children: const [
Icon(Icons.delete_outline),
Padding(
padding: EdgeInsets.all(8),
),
Text("Delete album"),
],
),
Text(isArchived ? "Unhide album" : "Hide album"),
],
),
),
);
if (widget.collection.type != CollectionType.favorites) {
items.add(
PopupMenuItem(
value: 3,
child: Row(
children: const [
Icon(Icons.delete_outline),
Padding(
padding: EdgeInsets.all(8),
),
);
}
return items;
},
onSelected: (value) async {
if (value == 1) {
await _renameAlbum(context);
} else if (value == 2) {
await changeCollectionVisibility(
context,
widget.collection,
widget.collection.isArchived()
? visibilityVisible
: visibilityArchive,
);
} else if (value == 3) {
await _trashCollection();
}
},
Text("Delete album"),
],
),
),
);
}
} // ownedCollection open ends
if (widget.type == GalleryType.sharedCollection) {
items.add(
PopupMenuItem(
value: 4,
child: Row(
children: const [
Icon(Icons.logout),
Padding(
padding: EdgeInsets.all(8),
),
Text("Leave album"),
],
),
),
);
}
actions.add(
PopupMenuButton(
itemBuilder: (context) {
return items;
},
onSelected: (value) async {
if (value == 1) {
await _renameAlbum(context);
} else if (value == 2) {
await changeCollectionVisibility(
context,
widget.collection,
widget.collection.isArchived()
? visibilityVisible
: visibilityArchive,
);
} else if (value == 3) {
await _trashCollection();
} else if (value == 4) {
await _leaveAlbum(context);
} else {
showToast(context, "Something went wrong");
}
},
),
);
return actions;
}

View file

@ -33,6 +33,7 @@ class _SearchIconWidgetState extends State<SearchIconWidget> {
return Hero(
tag: "search_icon",
child: IconButton(
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
onPressed: () {
Navigator.push(
context,

View file

@ -3,10 +3,8 @@
import 'dart:convert';
import 'dart:math';
import 'package:dio/dio.dart';
import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/network.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/models/file.dart';
@ -16,7 +14,7 @@ import 'package:photos/utils/file_download_util.dart';
class DiffFetcher {
final _logger = Logger("DiffFetcher");
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
Future<Diff> getEncryptedFilesDiff(int collectionID, int sinceTime) async {
_logger.info(
@ -26,11 +24,8 @@ class DiffFetcher {
sinceTime.toString(),
);
try {
final response = await _dio.get(
Configuration.instance.getHttpEndpoint() + "/collections/v2/diff",
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
final response = await _enteDio.get(
"/collections/v2/diff",
queryParameters: {
"collectionID": collectionID,
"sinceTime": sinceTime,

View file

@ -46,6 +46,7 @@ class FileUploader {
final _logger = Logger("FileUploader");
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
final LinkedHashMap _queue = LinkedHashMap<String, FileUploadItem>();
final _uploadLocks = UploadLocksDB.instance;
final kSafeBufferForLockExpiry = const Duration(days: 1).inMicroseconds;
@ -186,7 +187,8 @@ class FileUploader {
for (final id in uploadsToBeRemoved) {
_queue.remove(id).completer.completeError(reason);
}
_logger.info('number of enteries removed from queue ${uploadsToBeRemoved.length}');
_logger.info(
'number of enteries removed from queue ${uploadsToBeRemoved.length}');
_totalCountInUploadSession -= uploadsToBeRemoved.length;
}
@ -670,13 +672,7 @@ class FileUploader {
}
};
try {
final response = await _dio.post(
Configuration.instance.getHttpEndpoint() + "/files",
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
data: request,
);
final response = await _enteDio.post("/files", data: request);
final data = response.data;
file.uploadedFileID = data["id"];
file.collectionID = collectionID;
@ -747,13 +743,7 @@ class FileUploader {
}
};
try {
final response = await _dio.put(
Configuration.instance.getHttpEndpoint() + "/files/update",
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
data: request,
);
final response = await _enteDio.put("/files/update", data: request);
final data = response.data;
file.uploadedFileID = data["id"];
file.updationTime = data["updationTime"];
@ -805,14 +795,11 @@ class FileUploader {
Future<void> fetchUploadURLs(int fileCount) async {
_uploadURLFetchInProgress ??= Future<void>(() async {
try {
final response = await _dio.get(
Configuration.instance.getHttpEndpoint() + "/files/upload-urls",
final response = await _enteDio.get(
"/files/upload-urls",
queryParameters: {
"count": min(42, fileCount * 2), // m4gic number
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
);
final urls = (response.data["urls"] as List)
.map((e) => UploadURL.fromMap(e))

View file

@ -1,67 +0,0 @@
import "dart:convert";
import "dart:typed_data";
const String _alphabet = "0123456789abcdef";
/// An instance of the default implementation of the [HexCodec].
const hex = HexCodec();
/// A codec for encoding and decoding byte arrays to and from
/// hexadecimal strings.
class HexCodec extends Codec<List<int>, String> {
const HexCodec();
@override
Converter<List<int>, String> get encoder => const HexEncoder();
@override
Converter<String, List<int>> get decoder => const HexDecoder();
}
/// A converter to encode byte arrays into hexadecimal strings.
class HexEncoder extends Converter<List<int>, String> {
/// If true, the encoder will encode into uppercase hexadecimal strings.
final bool upperCase;
const HexEncoder({this.upperCase = false});
@override
String convert(List<int> bytes) {
final StringBuffer buffer = StringBuffer();
for (int part in bytes) {
if (part & 0xff != part) {
throw const FormatException("Non-byte integer detected");
}
buffer.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
}
if (upperCase) {
return buffer.toString().toUpperCase();
} else {
return buffer.toString();
}
}
}
/// A converter to decode hexadecimal strings into byte arrays.
class HexDecoder extends Converter<String, List<int>> {
const HexDecoder();
@override
List<int> convert(String hex) {
String str = hex.replaceAll(" ", "");
str = str.toLowerCase();
if (str.length % 2 != 0) {
str = "0" + str;
}
final Uint8List result = Uint8List(str.length ~/ 2);
for (int i = 0; i < result.length; i++) {
final int firstDigit = _alphabet.indexOf(str[i * 2]);
final int secondDigit = _alphabet.indexOf(str[i * 2 + 1]);
if (firstDigit == -1 || secondDigit == -1) {
throw FormatException("Non-hex character detected in $hex");
}
result[i] = (firstDigit << 4) + secondDigit;
}
return result;
}
}

View file

@ -3,10 +3,8 @@
import 'dart:convert';
import 'dart:math';
import 'package:dio/dio.dart';
import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/network.dart';
import 'package:photos/models/magic_metadata.dart';
import 'package:photos/models/trash_file.dart';
@ -15,15 +13,12 @@ import 'package:photos/utils/file_download_util.dart';
class TrashDiffFetcher {
final _logger = Logger("TrashDiffFetcher");
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
Future<Diff> getTrashFilesDiff(int sinceTime) async {
try {
final response = await _dio.get(
Configuration.instance.getHttpEndpoint() + "/trash/v2/diff",
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()},
),
final response = await _enteDio.get(
"/trash/v2/diff",
queryParameters: {
"sinceTime": sinceTime,
},

View file

@ -35,14 +35,14 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.9.0"
version: "2.8.2"
background_fetch:
dependency: "direct main"
description:
name: background_fetch
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
bip39:
dependency: "direct main"
description:
@ -63,28 +63,35 @@ packages:
name: cached_network_image
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.2"
version: "3.2.1"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "1.0.0"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
version: "1.0.1"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
version: "1.2.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
chewie:
dependency: "direct main"
description:
@ -98,7 +105,7 @@ packages:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
version: "1.1.0"
collection:
dependency: "direct main"
description:
@ -225,6 +232,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.17"
equatable:
dependency: "direct main"
description:
name: equatable
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
event_bus:
dependency: "direct main"
description:
@ -259,21 +273,21 @@ packages:
name: extended_image
url: "https://pub.dartlang.org"
source: hosted
version: "6.3.1"
version: "6.2.1"
extended_image_library:
dependency: transitive
description:
name: extended_image_library
url: "https://pub.dartlang.org"
source: hosted
version: "3.4.0"
version: "3.3.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
version: "1.3.0"
fast_base58:
dependency: "direct main"
description:
@ -397,7 +411,7 @@ packages:
name: flutter_inappwebview
url: "https://pub.dartlang.org"
source: hosted
version: "5.4.3+7"
version: "5.5.0+2"
flutter_keyboard_visibility:
dependency: transitive
description:
@ -465,7 +479,7 @@ packages:
name: flutter_native_splash
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.10+1"
version: "2.2.9"
flutter_password_strength:
dependency: "direct main"
description:
@ -601,7 +615,28 @@ packages:
name: in_app_purchase
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.2"
version: "3.0.7"
in_app_purchase_android:
dependency: transitive
description:
name: in_app_purchase_android
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.3+5"
in_app_purchase_platform_interface:
dependency: transitive
description:
name: in_app_purchase_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.2"
in_app_purchase_storekit:
dependency: transitive
description:
name: in_app_purchase_storekit
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.2+2"
intl:
dependency: "direct main"
description:
@ -630,6 +665,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
lint:
dependency: transitive
description:
name: lint
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
lints:
dependency: transitive
description:
@ -664,28 +706,37 @@ packages:
name: lottie
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.3"
version: "1.4.2"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.12"
version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5"
version: "0.1.4"
media_extension:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: "68ffdad3db713b630e0d52edc2a45d23b438de6e"
url: "https://github.com/ente-io/media_extension.git"
source: git
version: "0.0.3"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.7.0"
mime:
dependency: transitive
description:
@ -792,7 +843,7 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.2"
version: "1.8.1"
path_drawing:
dependency: transitive
description:
@ -957,17 +1008,17 @@ packages:
sentry:
dependency: "direct main"
description:
path: "thirdparty/sentry-dart/dart"
relative: true
source: path
version: "6.11.0"
name: sentry
url: "https://pub.dartlang.org"
source: hosted
version: "6.12.1"
sentry_flutter:
dependency: "direct main"
description:
path: "thirdparty/sentry-dart/flutter"
relative: true
source: path
version: "6.11.0"
name: sentry_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "6.12.1"
share_plus:
dependency: "direct main"
description:
@ -1077,7 +1128,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.0"
version: "1.8.2"
sprintf:
dependency: transitive
description:
@ -1091,7 +1142,7 @@ packages:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.0.3+1"
sqflite_common:
dependency: transitive
description:
@ -1133,7 +1184,7 @@ packages:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
version: "1.1.0"
syncfusion_flutter_core:
dependency: "direct main"
description:
@ -1161,14 +1212,14 @@ packages:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.12"
version: "0.4.9"
timezone:
dependency: transitive
description:
@ -1394,5 +1445,5 @@ packages:
source: hosted
version: "3.1.1"
sdks:
dart: ">=2.18.0 <3.0.0"
flutter: ">=3.3.0"
dart: ">=2.17.0 <3.0.0"
flutter: ">=3.0.0"

View file

@ -11,16 +11,17 @@ description: ente photos application
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.6.40+370
version: 0.6.47+377
environment:
sdk: '>=2.12.0 <3.0.0'
sdk: '>=2.17.0 <3.0.0'
dependencies:
adaptive_theme: ^3.1.0
animate_do: ^2.0.0
archive: ^3.1.2
background_fetch: ^1.0.1
background_fetch: ^1.1.1
bip39: ^1.0.6
cached_network_image: ^3.0.0
chewie:
@ -35,6 +36,7 @@ dependencies:
dots_indicator: ^2.0.0
dotted_border: ^2.0.0+2
email_validator: ^2.0.1
equatable: ^2.0.5
event_bus: ^2.0.0
exif: ^3.0.0
expandable: ^5.0.1
@ -51,7 +53,7 @@ dependencies:
flutter_easyloading: ^3.0.0
flutter_email_sender: ^5.1.0
flutter_image_compress: ^1.1.0
flutter_inappwebview: ^5.3.2
flutter_inappwebview: ^5.5.0+2
flutter_launcher_icons: ^0.9.3
flutter_local_notifications: ^9.5.3+1
flutter_localizations:
@ -68,13 +70,15 @@ dependencies:
image: ^3.0.2
image_editor: ^1.0.0
implicitly_animated_reorderable_list: ^0.4.0
in_app_purchase: ^0.5.2
in_app_purchase: ^3.0.7
intl: ^0.17.0
like_button: ^2.0.2
loading_animations: ^2.1.0
local_auth: ^1.1.5
logging: ^1.0.1
lottie: ^1.2.2
media_extension:
git: "https://github.com/ente-io/media_extension.git"
motionphoto:
git: "https://github.com/ente-io/motionphoto.git"
move_to_background: ^1.0.2
@ -94,10 +98,8 @@ dependencies:
quiver: ^3.0.1
receive_sharing_intent: ^1.4.5
scrollable_positioned_list: ^0.2.2
sentry:
path: thirdparty/sentry-dart/dart
sentry_flutter:
path: thirdparty/sentry-dart/flutter
sentry: ^6.12.1
sentry_flutter: ^6.12.1
share_plus: ^4.0.10
shared_preferences: ^2.0.5
sqflite: ^2.0.0+3

@ -1 +0,0 @@
Subproject commit 2cd4193c7a938d734bcddce514cba36040ca6035