Merge branch 'master' into photo_manager_update
This commit is contained in:
commit
86ac665365
64 changed files with 1299 additions and 1545 deletions
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
@ -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
5
.gitmodules
vendored
|
@ -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
7
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.background": "#123046",
|
||||
"titleBar.activeBackground": "#194363",
|
||||
"titleBar.activeForeground": "#F9FBFD"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
13
lib/core/cache/thumbnail_cache_manager.dart
vendored
13
lib/core/cache/thumbnail_cache_manager.dart
vendored
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
3
lib/events/opened_settings_event.dart
Normal file
3
lib/events/opened_settings_event.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
import 'package:photos/events/event.dart';
|
||||
|
||||
class OpenedSettingsEvent extends Event {}
|
|
@ -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();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:photos/models/trash_file.dart';
|
||||
|
||||
const kIgnoreReasonTrash = "trash";
|
||||
const kIgnoreReasonInvalidFile = "invalidFile";
|
||||
|
||||
class IgnoredFile {
|
||||
final String? localID;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 {
|
||||
|
|
90
lib/states/user_details_state.dart
Normal file
90
lib/states/user_details_state.dart
Normal 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;
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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: () {},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
27
lib/ui/components/brand_title_widget.dart
Normal file
27
lib/ui/components/brand_title_widget.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
46
lib/ui/components/home_header_widget.dart
Normal file
46
lib/ui/components/home_header_widget.dart
Normal 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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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],
|
||||
),
|
||||
|
|
|
@ -113,6 +113,7 @@ class AccountSectionWidget extends StatelessWidget {
|
|||
}
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,5 +10,6 @@ ExpandableThemeData getExpandableTheme(BuildContext context) {
|
|||
useInkWell: false,
|
||||
tapBodyToCollapse: true,
|
||||
tapBodyToExpand: true,
|
||||
animationDuration: Duration(milliseconds: 400),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
61
lib/ui/settings/settings_title_bar_widget.dart
Normal file
61
lib/ui/settings/settings_title_bar_widget.dart
Normal 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();
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
|
|
117
pubspec.lock
117
pubspec.lock
|
@ -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"
|
||||
|
|
20
pubspec.yaml
20
pubspec.yaml
|
@ -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
thirdparty/sentry-dart
vendored
1
thirdparty/sentry-dart
vendored
|
@ -1 +0,0 @@
|
|||
Subproject commit 2cd4193c7a938d734bcddce514cba36040ca6035
|
Loading…
Reference in a new issue