Merge branch 'master' into cherry_pick_release
This commit is contained in:
commit
0cdc0ae466
36 changed files with 1486 additions and 1004 deletions
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
|
|
@ -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)
|
||||
|
@ -145,13 +145,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,7 +190,7 @@ 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`)
|
||||
- motionphoto (from `.symlinks/plugins/motionphoto/ios`)
|
||||
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
||||
|
@ -266,8 +266,8 @@ 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"
|
||||
motionphoto:
|
||||
|
@ -330,7 +330,7 @@ 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
|
||||
|
@ -347,8 +347,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,7 +286,7 @@
|
|||
"${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}/motionphoto/motionphoto.framework",
|
||||
|
@ -339,7 +339,7 @@
|
|||
"${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}/motionphoto.framework",
|
||||
|
|
|
@ -341,10 +341,6 @@ extension CustomColorScheme on ColorScheme {
|
|||
? const Color.fromRGBO(180, 180, 180, 1)
|
||||
: const Color.fromRGBO(100, 100, 100, 1);
|
||||
|
||||
Color get themeSwitchInactiveIconColor => brightness == Brightness.light
|
||||
? const Color.fromRGBO(0, 0, 0, 1).withOpacity(0.5)
|
||||
: const Color.fromRGBO(255, 255, 255, 1).withOpacity(0.5);
|
||||
|
||||
Color get searchResultsColor => brightness == Brightness.light
|
||||
? const Color.fromRGBO(245, 245, 245, 1.0)
|
||||
: const Color.fromRGBO(30, 30, 30, 1.0);
|
||||
|
|
|
@ -15,4 +15,5 @@ enum TabChangedEventSource {
|
|||
pageView,
|
||||
collectionsPage,
|
||||
backButton,
|
||||
settingsTitleBar
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
@ -128,7 +127,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,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;
|
||||
}
|
||||
|
|
|
@ -38,12 +38,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 +53,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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -292,7 +292,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(
|
||||
|
@ -334,6 +334,23 @@ class CollectionsService {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> leaveAlbum(Collection collection) async {
|
||||
try {
|
||||
await _dio.post(
|
||||
Configuration.instance.getHttpEndpoint() +
|
||||
"/collections/leave/${collection.id}",
|
||||
options: Options(
|
||||
headers: {"X-Auth-Token": Configuration.instance.getToken()},
|
||||
),
|
||||
);
|
||||
// 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,
|
||||
|
|
82
lib/states/user_details_state.dart
Normal file
82
lib/states/user_details_state.dart
Normal file
|
@ -0,0 +1,82 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.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: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<TabChangedEvent> _tabChangedEventSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_fetchUserDetails();
|
||||
_userDetailsChangedEvent =
|
||||
Bus.instance.on<UserDetailsChangedEvent>().listen((event) {
|
||||
_fetchUserDetails();
|
||||
});
|
||||
_tabChangedEventSubscription =
|
||||
Bus.instance.on<TabChangedEvent>().listen((event) {
|
||||
if (event.selectedIndex == 3) {
|
||||
_fetchUserDetails();
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_userDetailsChangedEvent.cancel();
|
||||
_tabChangedEventSubscription.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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
57
lib/ui/components/captioned_text_widget.dart
Normal file
57
lib/ui/components/captioned_text_widget.dart
Normal file
|
@ -0,0 +1,57 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/ente_theme_data.dart';
|
||||
|
||||
class CaptionedTextWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final String? subTitle;
|
||||
final TextStyle? textStyle;
|
||||
final bool makeTextBold;
|
||||
final Color? textColor;
|
||||
const CaptionedTextWidget({
|
||||
required this.title,
|
||||
this.subTitle,
|
||||
this.textStyle,
|
||||
this.makeTextBold = false,
|
||||
this.textColor,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final enteColorScheme = Theme.of(context).colorScheme.enteTheme.colorScheme;
|
||||
final enteTextTheme = Theme.of(context).colorScheme.enteTheme.textTheme;
|
||||
|
||||
return Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: textStyle ??
|
||||
(makeTextBold
|
||||
? enteTextTheme.bodyBold.copyWith(color: textColor)
|
||||
: enteTextTheme.body.copyWith(color: textColor)),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: title,
|
||||
),
|
||||
subTitle != null
|
||||
? TextSpan(
|
||||
text: ' \u2022 $subTitle',
|
||||
style: enteTextTheme.small.copyWith(
|
||||
color: enteColorScheme.textMuted,
|
||||
),
|
||||
)
|
||||
: const TextSpan(text: ''),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
65
lib/ui/components/expandable_menu_item_widget.dart
Normal file
65
lib/ui/components/expandable_menu_item_widget.dart
Normal file
|
@ -0,0 +1,65 @@
|
|||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/ente_theme_data.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
|
||||
class ExpandableMenuItemWidget extends StatefulWidget {
|
||||
final String title;
|
||||
final Widget selectionOptionsWidget;
|
||||
final IconData leadingIcon;
|
||||
const ExpandableMenuItemWidget({
|
||||
required this.title,
|
||||
required this.selectionOptionsWidget,
|
||||
required this.leadingIcon,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ExpandableMenuItemWidget> createState() =>
|
||||
_ExpandableMenuItemWidgetState();
|
||||
}
|
||||
|
||||
class _ExpandableMenuItemWidgetState extends State<ExpandableMenuItemWidget> {
|
||||
final expandableController = ExpandableController(initialExpanded: false);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
expandableController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final enteColorScheme = Theme.of(context).colorScheme.enteTheme.colorScheme;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: enteColorScheme.backgroundElevated2,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
140
lib/ui/components/menu_item_widget.dart
Normal file
140
lib/ui/components/menu_item_widget.dart
Normal file
|
@ -0,0 +1,140 @@
|
|||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/ente_theme_data.dart';
|
||||
|
||||
class MenuItemWidget extends StatefulWidget {
|
||||
final Widget captionedTextWidget;
|
||||
final bool isHeaderOfExpansion;
|
||||
// leading icon can be passed without specifing size of icon, this component sets size to 20x20 irrespective of passed icon's size
|
||||
final IconData? leadingIcon;
|
||||
final Color? leadingIconColor;
|
||||
// trailing icon can be passed without size as default size set by flutter is what this component expects
|
||||
final IconData? trailingIcon;
|
||||
final Widget? trailingSwitch;
|
||||
final bool trailingIconIsMuted;
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onDoubleTap;
|
||||
final Color? menuItemColor;
|
||||
final bool alignCaptionedTextToLeft;
|
||||
final double borderRadius;
|
||||
final ExpandableController? expandableController;
|
||||
const MenuItemWidget({
|
||||
required this.captionedTextWidget,
|
||||
this.isHeaderOfExpansion = false,
|
||||
this.leadingIcon,
|
||||
this.leadingIconColor,
|
||||
this.trailingIcon,
|
||||
this.trailingSwitch,
|
||||
this.trailingIconIsMuted = false,
|
||||
this.onTap,
|
||||
this.onDoubleTap,
|
||||
this.menuItemColor,
|
||||
this.alignCaptionedTextToLeft = false,
|
||||
this.borderRadius = 4.0,
|
||||
this.expandableController,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MenuItemWidget> createState() => _MenuItemWidgetState();
|
||||
}
|
||||
|
||||
class _MenuItemWidgetState extends State<MenuItemWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
if (widget.expandableController != null) {
|
||||
widget.expandableController!.addListener(() {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (widget.expandableController != null) {
|
||||
widget.expandableController!.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.isHeaderOfExpansion
|
||||
? menuItemWidget(context)
|
||||
: GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
onDoubleTap: widget.onDoubleTap,
|
||||
child: menuItemWidget(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget menuItemWidget(BuildContext context) {
|
||||
final enteColorScheme = Theme.of(context).colorScheme.enteTheme.colorScheme;
|
||||
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.only(
|
||||
topLeft: borderRadius,
|
||||
topRight: borderRadius,
|
||||
bottomLeft: bottomBorderRadius,
|
||||
bottomRight: bottomBorderRadius,
|
||||
),
|
||||
color: widget.menuItemColor,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
widget.alignCaptionedTextToLeft && widget.leadingIcon == null
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: widget.leadingIcon == null
|
||||
? const SizedBox.shrink()
|
||||
: FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
child: Icon(
|
||||
widget.leadingIcon,
|
||||
color: widget.leadingIconColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
widget.captionedTextWidget,
|
||||
widget.expandableController != null
|
||||
? AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
opacity: isExpanded! ? 0 : 1,
|
||||
child: AnimatedSwitcher(
|
||||
transitionBuilder: (child, animation) {
|
||||
return ScaleTransition(scale: animation, child: child);
|
||||
},
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: isExpanded!
|
||||
? const SizedBox.shrink()
|
||||
: Icon(widget.trailingIcon),
|
||||
),
|
||||
)
|
||||
: widget.trailingIcon != null
|
||||
? Icon(
|
||||
widget.trailingIcon,
|
||||
color: widget.trailingIconIsMuted
|
||||
? enteColorScheme.strokeMuted
|
||||
: null,
|
||||
)
|
||||
: widget.trailingSwitch ?? const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
40
lib/ui/components/toggle_switch_widget.dart
Normal file
40
lib/ui/components/toggle_switch_widget.dart
Normal file
|
@ -0,0 +1,40 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/ente_theme_data.dart';
|
||||
|
||||
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);
|
||||
|
||||
@override
|
||||
State<ToggleSwitchWidget> createState() => _ToggleSwitchWidgetState();
|
||||
}
|
||||
|
||||
class _ToggleSwitchWidgetState extends State<ToggleSwitchWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final enteColorScheme = Theme.of(context).colorScheme.enteTheme.colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: SizedBox(
|
||||
height: 30,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
child: Switch.adaptive(
|
||||
activeColor: enteColorScheme.primary400,
|
||||
inactiveTrackColor: enteColorScheme.fillMuted,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
value: widget.value,
|
||||
onChanged: widget.onChanged,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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],
|
||||
),
|
||||
|
|
127
lib/ui/settings/about_section_widget.dart
Normal file
127
lib/ui/settings/about_section_widget.dart
Normal file
|
@ -0,0 +1,127 @@
|
|||
// @dart=2.9
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/services/update_service.dart';
|
||||
import 'package:photos/ui/common/web_page.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget.dart';
|
||||
import 'package:photos/ui/settings/app_update_dialog.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
import 'package:photos/utils/dialog_util.dart';
|
||||
import 'package:photos/utils/toast_util.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AboutSectionWidget extends StatelessWidget {
|
||||
const AboutSectionWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandableMenuItemWidget(
|
||||
title: "About",
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.info_outline,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
sectionOptionSpacing,
|
||||
const AboutMenuItemWidget(
|
||||
title: "FAQ",
|
||||
url: "https://ente.io/faq",
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
const AboutMenuItemWidget(
|
||||
title: "Terms",
|
||||
url: "https://ente.io/terms",
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
const AboutMenuItemWidget(
|
||||
title: "Privacy",
|
||||
url: "https://ente.io/privacy",
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Source code",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
launchUrl(Uri.parse("https://github.com/ente-io/frame"));
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
UpdateService.instance.isIndependent()
|
||||
? Column(
|
||||
children: [
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Check for updates",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final dialog =
|
||||
createProgressDialog(context, "Checking...");
|
||||
await dialog.show();
|
||||
final shouldUpdate =
|
||||
await UpdateService.instance.shouldUpdate();
|
||||
await dialog.hide();
|
||||
if (shouldUpdate) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AppUpdateDialog(
|
||||
UpdateService.instance.getLatestVersionInfo(),
|
||||
);
|
||||
},
|
||||
barrierColor: Colors.black.withOpacity(0.85),
|
||||
);
|
||||
} else {
|
||||
showToast(context, "You are on the latest version");
|
||||
}
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AboutMenuItemWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final String url;
|
||||
final String webPageTitle;
|
||||
const AboutMenuItemWidget({
|
||||
@required this.title,
|
||||
@required this.url,
|
||||
this.webPageTitle,
|
||||
Key key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: title,
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(webPageTitle ?? title, url);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
// @dart=2.9
|
||||
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:photos/services/local_authentication_service.dart';
|
||||
|
@ -8,35 +7,35 @@ import 'package:photos/services/user_service.dart';
|
|||
import 'package:photos/ui/account/change_email_dialog.dart';
|
||||
import 'package:photos/ui/account/password_entry_page.dart';
|
||||
import 'package:photos/ui/account/recovery_key_page.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
import 'package:photos/ui/settings/settings_section_title.dart';
|
||||
import 'package:photos/ui/settings/settings_text_item.dart';
|
||||
import 'package:photos/utils/dialog_util.dart';
|
||||
import 'package:photos/utils/navigation_util.dart';
|
||||
|
||||
class AccountSectionWidget extends StatefulWidget {
|
||||
class AccountSectionWidget extends StatelessWidget {
|
||||
const AccountSectionWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
AccountSectionWidgetState createState() => AccountSectionWidgetState();
|
||||
}
|
||||
|
||||
class AccountSectionWidgetState extends State<AccountSectionWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandablePanel(
|
||||
header: const SettingsSectionTitle("Account"),
|
||||
collapsed: const SizedBox.shrink(),
|
||||
expanded: _getSectionOptions(context),
|
||||
theme: getExpandableTheme(context),
|
||||
return ExpandableMenuItemWidget(
|
||||
title: "Account",
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.account_circle_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Column _getSectionOptions(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Recovery key",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(
|
||||
|
@ -46,7 +45,7 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
|
|||
if (hasAuthenticated) {
|
||||
String recoveryKey;
|
||||
try {
|
||||
recoveryKey = await _getOrCreateRecoveryKey();
|
||||
recoveryKey = await _getOrCreateRecoveryKey(context);
|
||||
} catch (e) {
|
||||
showGenericErrorDialog(context);
|
||||
return;
|
||||
|
@ -62,14 +61,14 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
|
|||
);
|
||||
}
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Recovery key",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Change email",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(
|
||||
|
@ -87,14 +86,14 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
|
|||
);
|
||||
}
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Change email",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Change password",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(
|
||||
|
@ -113,16 +112,13 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
|
|||
);
|
||||
}
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Change password",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> _getOrCreateRecoveryKey() async {
|
||||
Future<String> _getOrCreateRecoveryKey(BuildContext context) async {
|
||||
return Sodium.bin2hex(
|
||||
await UserService.instance.getOrCreateRecoveryKey(context),
|
||||
);
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/ente_theme_data.dart';
|
||||
|
@ -12,9 +11,11 @@ import 'package:photos/services/deduplication_service.dart';
|
|||
import 'package:photos/services/sync_service.dart';
|
||||
import 'package:photos/ui/backup_folder_selection_page.dart';
|
||||
import 'package:photos/ui/common/dialogs.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/toggle_switch_widget.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
import 'package:photos/ui/settings/settings_section_title.dart';
|
||||
import 'package:photos/ui/settings/settings_text_item.dart';
|
||||
import 'package:photos/ui/tools/deduplicate_page.dart';
|
||||
import 'package:photos/ui/tools/free_space_page.dart';
|
||||
import 'package:photos/utils/data_util.dart';
|
||||
|
@ -33,19 +34,23 @@ class BackupSectionWidget extends StatefulWidget {
|
|||
class BackupSectionWidgetState extends State<BackupSectionWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandablePanel(
|
||||
header: const SettingsSectionTitle("Backup"),
|
||||
collapsed: Container(),
|
||||
expanded: _getSectionOptions(context),
|
||||
theme: getExpandableTheme(context),
|
||||
return ExpandableMenuItemWidget(
|
||||
title: "Backup",
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.backup_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
final List<Widget> sectionOptions = [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () async {
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Backed up folders",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () {
|
||||
routeToPage(
|
||||
context,
|
||||
const BackupFolderSelectionPage(
|
||||
|
@ -53,93 +58,72 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
|
|||
),
|
||||
);
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Backed up folders",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Backup over mobile data",
|
||||
),
|
||||
trailingSwitch: ToggleSwitchWidget(
|
||||
value: Configuration.instance.shouldBackupOverMobileData(),
|
||||
onChanged: (value) async {
|
||||
Configuration.instance.setBackupOverMobileData(value);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Backup over mobile data",
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
Switch.adaptive(
|
||||
value: Configuration.instance.shouldBackupOverMobileData(),
|
||||
onChanged: (value) async {
|
||||
Configuration.instance.setBackupOverMobileData(value);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Backup videos",
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
Switch.adaptive(
|
||||
value: Configuration.instance.shouldBackupVideos(),
|
||||
onChanged: (value) async {
|
||||
Configuration.instance.setShouldBackupVideos(value);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Backup videos",
|
||||
),
|
||||
trailingSwitch: ToggleSwitchWidget(
|
||||
value: Configuration.instance.shouldBackupVideos(),
|
||||
onChanged: (value) async {
|
||||
Configuration.instance.setShouldBackupVideos(value);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
];
|
||||
if (Platform.isIOS) {
|
||||
sectionOptions.addAll([
|
||||
sectionOptionDivider,
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Disable auto lock",
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
Switch.adaptive(
|
||||
value: Configuration.instance.shouldKeepDeviceAwake(),
|
||||
onChanged: (value) async {
|
||||
if (value) {
|
||||
final choice = await showChoiceDialog(
|
||||
context,
|
||||
"Disable automatic screen lock when ente is running?",
|
||||
"This will ensure faster uploads by ensuring your device does not sleep when uploads are in progress.",
|
||||
firstAction: "No",
|
||||
secondAction: "Yes",
|
||||
);
|
||||
if (choice != DialogUserChoice.secondChoice) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await Configuration.instance.setShouldKeepDeviceAwake(value);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Disable auto lock",
|
||||
),
|
||||
trailingSwitch: ToggleSwitchWidget(
|
||||
value: Configuration.instance.shouldKeepDeviceAwake(),
|
||||
onChanged: (value) async {
|
||||
if (value) {
|
||||
final choice = await showChoiceDialog(
|
||||
context,
|
||||
"Disable automatic screen lock when ente is running?",
|
||||
"This will ensure faster uploads by ensuring your device does not sleep when uploads are in progress.",
|
||||
firstAction: "No",
|
||||
secondAction: "Yes",
|
||||
);
|
||||
if (choice != DialogUserChoice.secondChoice) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await Configuration.instance.setShouldKeepDeviceAwake(value);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
]);
|
||||
}
|
||||
sectionOptions.addAll(
|
||||
[
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Free up space",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final dialog = createProgressDialog(context, "Calculating...");
|
||||
await dialog.show();
|
||||
|
@ -160,20 +144,21 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
|
|||
"You've no files on this device that can be deleted",
|
||||
);
|
||||
} else {
|
||||
final bool result = await routeToPage(context, FreeSpacePage(status));
|
||||
final bool result =
|
||||
await routeToPage(context, FreeSpacePage(status));
|
||||
if (result == true) {
|
||||
_showSpaceFreedDialog(status);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Free up space",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Deduplicate files",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final dialog = createProgressDialog(context, "Calculating...");
|
||||
await dialog.show();
|
||||
|
@ -202,11 +187,8 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
|
|||
}
|
||||
}
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Deduplicate files",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
return Column(
|
||||
|
|
|
@ -1,23 +1,15 @@
|
|||
// @dart=2.9
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget sectionOptionDivider = Padding(
|
||||
padding: EdgeInsets.all(Platform.isIOS ? 4 : 2),
|
||||
);
|
||||
Widget sectionOptionSpacing = const SizedBox(height: 6);
|
||||
|
||||
ExpandableThemeData getExpandableTheme(BuildContext context) {
|
||||
return ExpandableThemeData(
|
||||
expandIcon: CupertinoIcons.chevron_down,
|
||||
collapseIcon: CupertinoIcons.chevron_up,
|
||||
iconPadding: const EdgeInsets.all(4),
|
||||
iconColor: Theme.of(context).colorScheme.onSurface,
|
||||
iconSize: 20.0,
|
||||
iconRotationAngle: -3.14 / 2,
|
||||
hasIcon: true,
|
||||
return const ExpandableThemeData(
|
||||
hasIcon: false,
|
||||
useInkWell: false,
|
||||
tapBodyToCollapse: true,
|
||||
tapBodyToExpand: true,
|
||||
animationDuration: Duration(milliseconds: 400),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,60 +1,58 @@
|
|||
// @dart=2.9
|
||||
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/ente_theme_data.dart';
|
||||
import 'package:photos/services/user_service.dart';
|
||||
import 'package:photos/ui/account/delete_account_page.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
import 'package:photos/ui/settings/settings_section_title.dart';
|
||||
import 'package:photos/ui/settings/settings_text_item.dart';
|
||||
import 'package:photos/utils/navigation_util.dart';
|
||||
|
||||
class DangerSectionWidget extends StatefulWidget {
|
||||
class DangerSectionWidget extends StatelessWidget {
|
||||
const DangerSectionWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DangerSectionWidget> createState() => _DangerSectionWidgetState();
|
||||
}
|
||||
|
||||
class _DangerSectionWidgetState extends State<DangerSectionWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandablePanel(
|
||||
header: const SettingsSectionTitle("Exit", color: Colors.red),
|
||||
collapsed: Container(),
|
||||
expanded: _getSectionOptions(context),
|
||||
theme: getExpandableTheme(context),
|
||||
return ExpandableMenuItemWidget(
|
||||
title: "Exit",
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.logout_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Logout",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () {
|
||||
_onLogoutTapped();
|
||||
_onLogoutTapped(context);
|
||||
},
|
||||
child:
|
||||
const SettingsTextItem(text: "Logout", icon: Icons.navigate_next),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () async {
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Delete account",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () {
|
||||
routeToPage(context, const DeleteAccountPage());
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Delete account",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLogoutTapped() async {
|
||||
Future<void> _onLogoutTapped(BuildContext context) async {
|
||||
final AlertDialog alert = AlertDialog(
|
||||
title: const Text(
|
||||
"Logout",
|
||||
|
|
|
@ -1,65 +1,69 @@
|
|||
// @dart=2.9
|
||||
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/services/ignored_files_service.dart';
|
||||
import 'package:photos/services/local_sync_service.dart';
|
||||
import 'package:photos/services/sync_service.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
import 'package:photos/ui/settings/settings_section_title.dart';
|
||||
import 'package:photos/ui/settings/settings_text_item.dart';
|
||||
import 'package:photos/utils/toast_util.dart';
|
||||
|
||||
class DebugSectionWidget extends StatelessWidget {
|
||||
const DebugSectionWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandablePanel(
|
||||
header: const SettingsSectionTitle("Debug"),
|
||||
collapsed: Container(),
|
||||
expanded: _getSectionOptions(context),
|
||||
theme: getExpandableTheme(context),
|
||||
return ExpandableMenuItemWidget(
|
||||
title: "Debug",
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.bug_report_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Key attributes",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
_showKeyAttributesDialog(context);
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Key attributes",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Delete Local Import DB",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
await LocalSyncService.instance.resetLocalSync();
|
||||
showToast(context, "Done");
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Delete Local Import DB",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Allow auto-upload for ignored files",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
await IgnoredFilesService.instance.reset();
|
||||
SyncService.instance.sync();
|
||||
showToast(context, "Done");
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Allow auto-upload for ignored files",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,45 +1,27 @@
|
|||
// @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,45 +36,40 @@ 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() {
|
||||
return SizedBox(
|
||||
width: 350,
|
||||
height: 175,
|
||||
// constraints: BoxConstraints(maxWidth: 390, maxHeight: 195),
|
||||
Widget containerForUserDetails(
|
||||
InheritedUserDetails inheritedUserDetails,
|
||||
) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 428, maxHeight: 175),
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
|
@ -103,162 +80,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(
|
||||
|
@ -271,4 +105,150 @@ 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,
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
// @dart=2.9
|
||||
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/services/update_service.dart';
|
||||
import 'package:photos/ui/common/web_page.dart';
|
||||
import 'package:photos/ui/settings/app_update_dialog.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
import 'package:photos/ui/settings/settings_section_title.dart';
|
||||
import 'package:photos/ui/settings/settings_text_item.dart';
|
||||
import 'package:photos/utils/dialog_util.dart';
|
||||
import 'package:photos/utils/toast_util.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class InfoSectionWidget extends StatelessWidget {
|
||||
const InfoSectionWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandablePanel(
|
||||
header: const SettingsSectionTitle("About"),
|
||||
collapsed: Container(),
|
||||
expanded: _getSectionOptions(context),
|
||||
theme: getExpandableTheme(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () async {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const WebPage("FAQ", "https://ente.io/faq");
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const SettingsTextItem(text: "FAQ", icon: Icons.navigate_next),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const WebPage("terms", "https://ente.io/terms");
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child:
|
||||
const SettingsTextItem(text: "Terms", icon: Icons.navigate_next),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const WebPage("privacy", "https://ente.io/privacy");
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Privacy",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () async {
|
||||
launchUrl(Uri.parse("https://github.com/ente-io/frame"));
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Source code",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
UpdateService.instance.isIndependent()
|
||||
? Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () async {
|
||||
final dialog =
|
||||
createProgressDialog(context, "Checking...");
|
||||
await dialog.show();
|
||||
final shouldUpdate =
|
||||
await UpdateService.instance.shouldUpdate();
|
||||
await dialog.hide();
|
||||
if (shouldUpdate) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AppUpdateDialog(
|
||||
UpdateService.instance.getLatestVersionInfo(),
|
||||
);
|
||||
},
|
||||
barrierColor: Colors.black.withOpacity(0.85),
|
||||
);
|
||||
} else {
|
||||
showToast(context, "You are on the latest version");
|
||||
}
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Check for updates",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_windowmanager/flutter_windowmanager.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
|
@ -14,9 +13,11 @@ import 'package:photos/services/local_authentication_service.dart';
|
|||
import 'package:photos/services/user_service.dart';
|
||||
import 'package:photos/ui/account/sessions_page.dart';
|
||||
import 'package:photos/ui/common/loading_widget.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/toggle_switch_widget.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
import 'package:photos/ui/settings/settings_section_title.dart';
|
||||
import 'package:photos/ui/settings/settings_text_item.dart';
|
||||
|
||||
class SecuritySectionWidget extends StatefulWidget {
|
||||
const SecuritySectionWidget({Key key}) : super(key: key);
|
||||
|
@ -49,11 +50,10 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandablePanel(
|
||||
header: const SettingsSectionTitle("Security"),
|
||||
collapsed: Container(),
|
||||
expanded: _getSectionOptions(context),
|
||||
theme: getExpandableTheme(context),
|
||||
return ExpandableMenuItemWidget(
|
||||
title: "Security",
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.local_police_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -62,21 +62,16 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
if (_config.hasConfiguredAccount()) {
|
||||
children.addAll(
|
||||
[
|
||||
const Padding(padding: EdgeInsets.all(2)),
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Two-factor",
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
sectionOptionSpacing,
|
||||
FutureBuilder(
|
||||
future: UserService.instance.fetchTwoFactorStatus(),
|
||||
builder: (_, snapshot) {
|
||||
return MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Two-factor",
|
||||
),
|
||||
FutureBuilder(
|
||||
future: UserService.instance.fetchTwoFactorStatus(),
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Switch.adaptive(
|
||||
trailingSwitch: snapshot.hasData
|
||||
? ToggleSwitchWidget(
|
||||
value: snapshot.data,
|
||||
onChanged: (value) async {
|
||||
final hasAuthenticated =
|
||||
|
@ -93,155 +88,136 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
return Icon(
|
||||
Icons.error_outline,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
);
|
||||
}
|
||||
return const EnteLoadingWidget();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: snapshot.hasError
|
||||
? const Icon(Icons.error_outline_outlined)
|
||||
: const EnteLoadingWidget(),
|
||||
);
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
children.addAll([
|
||||
sectionOptionDivider,
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Lockscreen",
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
Switch.adaptive(
|
||||
value: _config.shouldShowLockScreen(),
|
||||
onChanged: (value) async {
|
||||
final hasAuthenticated = await LocalAuthenticationService
|
||||
.instance
|
||||
.requestLocalAuthForLockScreen(
|
||||
context,
|
||||
value,
|
||||
"Please authenticate to change lockscreen setting",
|
||||
"To enable lockscreen, please setup device passcode or screen lock in your system settings.",
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Lockscreen",
|
||||
),
|
||||
trailingSwitch: ToggleSwitchWidget(
|
||||
value: _config.shouldShowLockScreen(),
|
||||
onChanged: (value) async {
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthForLockScreen(
|
||||
context,
|
||||
value,
|
||||
"Please authenticate to change lockscreen setting",
|
||||
"To enable lockscreen, please setup device passcode or screen lock in your system settings.",
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
]);
|
||||
if (Platform.isAndroid) {
|
||||
children.addAll(
|
||||
[
|
||||
sectionOptionDivider,
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Hide from recents",
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
Switch.adaptive(
|
||||
value: _config.shouldHideFromRecents(),
|
||||
onChanged: (value) async {
|
||||
if (value) {
|
||||
final AlertDialog alert = AlertDialog(
|
||||
title: const Text("Hide from recents?"),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: const [
|
||||
Text(
|
||||
"Hiding from the task switcher will prevent you from taking screenshots in this app.",
|
||||
style: TextStyle(
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
Padding(padding: EdgeInsets.all(8)),
|
||||
Text(
|
||||
"Are you sure?",
|
||||
style: TextStyle(
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
"No",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.defaultTextColor,
|
||||
),
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Hide from recents",
|
||||
),
|
||||
trailingSwitch: Switch.adaptive(
|
||||
value: _config.shouldHideFromRecents(),
|
||||
onChanged: (value) async {
|
||||
if (value) {
|
||||
final AlertDialog alert = AlertDialog(
|
||||
title: const Text("Hide from recents?"),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: const [
|
||||
Text(
|
||||
"Hiding from the task switcher will prevent you from taking screenshots in this app.",
|
||||
style: TextStyle(
|
||||
height: 1.5,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.pop('dialog');
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"Yes",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.defaultTextColor,
|
||||
),
|
||||
Padding(padding: EdgeInsets.all(8)),
|
||||
Text(
|
||||
"Are you sure?",
|
||||
style: TextStyle(
|
||||
height: 1.5,
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.pop('dialog');
|
||||
await _config.setShouldHideFromRecents(true);
|
||||
await FlutterWindowManager.addFlags(
|
||||
FlutterWindowManager.FLAG_SECURE,
|
||||
);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
"No",
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.defaultTextColor,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.pop('dialog');
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await _config.setShouldHideFromRecents(false);
|
||||
await FlutterWindowManager.clearFlags(
|
||||
FlutterWindowManager.FLAG_SECURE,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"Yes",
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.defaultTextColor,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.pop('dialog');
|
||||
await _config.setShouldHideFromRecents(true);
|
||||
await FlutterWindowManager.addFlags(
|
||||
FlutterWindowManager.FLAG_SECURE,
|
||||
);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await _config.setShouldHideFromRecents(false);
|
||||
await FlutterWindowManager.clearFlags(
|
||||
FlutterWindowManager.FLAG_SECURE,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
children.addAll([
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Active sessions",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(
|
||||
|
@ -258,11 +234,8 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
);
|
||||
}
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Active sessions",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
]);
|
||||
return Column(
|
||||
children: children,
|
||||
|
|
68
lib/ui/settings/settings_title_bar_widget.dart
Normal file
68
lib/ui/settings/settings_title_bar_widget.dart
Normal file
|
@ -0,0 +1,68 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/events/tab_changed_event.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(12, 0, 20, 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
|
||||
onPressed: () {
|
||||
Bus.instance.fire(
|
||||
TabChangedEvent(
|
||||
0,
|
||||
TabChangedEventSource.settingsTitleBar,
|
||||
),
|
||||
);
|
||||
},
|
||||
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();
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/services/update_service.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
import 'package:photos/ui/settings/settings_section_title.dart';
|
||||
import 'package:photos/ui/settings/settings_text_item.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class SocialSectionWidget extends StatelessWidget {
|
||||
|
@ -15,68 +15,57 @@ class SocialSectionWidget extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandablePanel(
|
||||
header: const SettingsSectionTitle("Social"),
|
||||
collapsed: Container(),
|
||||
expanded: _getSectionOptions(context),
|
||||
theme: getExpandableTheme(context),
|
||||
return ExpandableMenuItemWidget(
|
||||
title: "Social",
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.interests_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
final List<Widget> options = [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
launchUrlString("https://twitter.com/enteio");
|
||||
},
|
||||
child:
|
||||
const SettingsTextItem(text: "Twitter", icon: Icons.navigate_next),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
launchUrlString("https://ente.io/discord");
|
||||
},
|
||||
child:
|
||||
const SettingsTextItem(text: "Discord", icon: Icons.navigate_next),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
launchUrlString("https://reddit.com/r/enteio");
|
||||
},
|
||||
child:
|
||||
const SettingsTextItem(text: "Reddit", icon: Icons.navigate_next),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
const SocialsMenuItemWidget("Twitter", "https://twitter.com/enteio"),
|
||||
sectionOptionSpacing,
|
||||
const SocialsMenuItemWidget("Discord", "https://ente.io/discord"),
|
||||
sectionOptionSpacing,
|
||||
const SocialsMenuItemWidget("Reddit", "https://reddit.com/r/enteio"),
|
||||
sectionOptionSpacing,
|
||||
];
|
||||
if (!UpdateService.instance.isIndependent()) {
|
||||
options.addAll(
|
||||
[
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
if (Platform.isAndroid) {
|
||||
launchUrlString(
|
||||
"https://play.google.com/store/apps/details?id=io.ente.photos",
|
||||
);
|
||||
} else {
|
||||
launchUrlString(
|
||||
"https://apps.apple.com/in/app/ente-photos/id1542026904",
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Rate us! ✨",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
)
|
||||
SocialsMenuItemWidget(
|
||||
"Rate us! ✨",
|
||||
Platform.isAndroid
|
||||
? "https://play.google.com/store/apps/details?id=io.ente.photos"
|
||||
: "https://apps.apple.com/in/app/ente-photos/id1542026904",
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
return Column(children: options);
|
||||
}
|
||||
}
|
||||
|
||||
class SocialsMenuItemWidget extends StatelessWidget {
|
||||
final String text;
|
||||
final String urlSring;
|
||||
const SocialsMenuItemWidget(this.text, this.urlSring, {Key key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: text,
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () {
|
||||
launchUrlString(urlSring);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/core/constants.dart';
|
||||
import 'package:photos/ui/common/web_page.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
import 'package:photos/ui/settings/settings_section_title.dart';
|
||||
import 'package:photos/ui/settings/settings_text_item.dart';
|
||||
import 'package:photos/utils/email_util.dart';
|
||||
|
||||
class SupportSectionWidget extends StatelessWidget {
|
||||
|
@ -17,11 +17,10 @@ class SupportSectionWidget extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandablePanel(
|
||||
header: const SettingsSectionTitle("Support"),
|
||||
collapsed: Container(),
|
||||
expanded: _getSectionOptions(context),
|
||||
theme: getExpandableTheme(context),
|
||||
return ExpandableMenuItemWidget(
|
||||
title: "Support",
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.help_outline_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -30,17 +29,24 @@ class SupportSectionWidget extends StatelessWidget {
|
|||
Platform.isAndroid ? "android-bugs@ente.io" : "ios-bugs@ente.io";
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Email",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
await sendEmail(context, to: supportEmail);
|
||||
},
|
||||
child:
|
||||
const SettingsTextItem(text: "Email", icon: Icons.navigate_next),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Roadmap",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
|
@ -56,14 +62,14 @@ class SupportSectionWidget extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Roadmap",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionDivider,
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: const CaptionedTextWidget(
|
||||
title: "Report a bug",
|
||||
),
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
await sendLogs(context, "Report bug", bugsEmail);
|
||||
},
|
||||
|
@ -71,11 +77,8 @@ class SupportSectionWidget extends StatelessWidget {
|
|||
final zipFilePath = await getZippedLogsFile(context);
|
||||
await shareLogs(context, bugsEmail, zipFilePath);
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Report bug 🐞",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
// @dart=2.9
|
||||
|
||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:photos/ente_theme_data.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget.dart';
|
||||
import 'package:photos/ui/settings/common_settings.dart';
|
||||
|
||||
class ThemeSwitchWidget extends StatefulWidget {
|
||||
const ThemeSwitchWidget({Key key}) : super(key: key);
|
||||
|
@ -12,13 +17,14 @@ class ThemeSwitchWidget extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _ThemeSwitchWidgetState extends State<ThemeSwitchWidget> {
|
||||
AdaptiveThemeMode themeMode;
|
||||
AdaptiveThemeMode currentThemeMode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
AdaptiveTheme.getThemeMode().then(
|
||||
(value) {
|
||||
themeMode = value ?? AdaptiveThemeMode.system;
|
||||
currentThemeMode = value ?? AdaptiveThemeMode.system;
|
||||
debugPrint('theme value $value');
|
||||
if (mounted) {
|
||||
setState(() => {});
|
||||
|
@ -27,44 +33,51 @@ class _ThemeSwitchWidgetState extends State<ThemeSwitchWidget> {
|
|||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
return ExpandableMenuItemWidget(
|
||||
title: "Theme",
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Theme.of(context).brightness == Brightness.light
|
||||
? Icons.light_mode_outlined
|
||||
: Icons.dark_mode_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
sectionOptionSpacing,
|
||||
_menuItem(context, AdaptiveThemeMode.light),
|
||||
sectionOptionSpacing,
|
||||
_menuItem(context, AdaptiveThemeMode.dark),
|
||||
sectionOptionSpacing,
|
||||
_menuItem(context, AdaptiveThemeMode.system),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _menuItem(BuildContext context, AdaptiveThemeMode themeMode) {
|
||||
return MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: toBeginningOfSentenceCase(themeMode.name),
|
||||
textStyle: Theme.of(context).colorScheme.enteTheme.textTheme.body,
|
||||
),
|
||||
isHeaderOfExpansion: false,
|
||||
trailingIcon: currentThemeMode == themeMode ? Icons.check : null,
|
||||
onTap: () async {
|
||||
await showCupertinoModalPopup(
|
||||
context: context,
|
||||
builder: (_) => CupertinoActionSheet(
|
||||
title: Text(
|
||||
"Theme",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headline4
|
||||
.copyWith(color: Colors.white),
|
||||
),
|
||||
actions: [
|
||||
for (var mode in AdaptiveThemeMode.values)
|
||||
CupertinoActionSheetAction(
|
||||
child: Text(mode.modeName),
|
||||
onPressed: () async {
|
||||
AdaptiveTheme.of(context).setThemeMode(mode);
|
||||
themeMode = mode;
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
if (mounted) {
|
||||
setState(() => {});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
child: const Text("Cancel"),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
AdaptiveTheme.of(context).setThemeMode(themeMode);
|
||||
currentThemeMode = themeMode;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
child: Text(themeMode?.modeName ?? ">"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,18 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/services/feature_flag_service.dart';
|
||||
import 'package:photos/states/user_details_state.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';
|
||||
import 'package:photos/ui/settings/backup_section_widget.dart';
|
||||
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/info_section_widget.dart';
|
||||
import 'package:photos/ui/settings/security_section_widget.dart';
|
||||
import 'package:photos/ui/settings/settings_section_title.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,17 +28,22 @@ class SettingsPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final enteColorScheme = getEnteColorScheme(context);
|
||||
return Scaffold(
|
||||
body: _getBody(context),
|
||||
body: Container(
|
||||
color: enteColorScheme.backgroundElevated,
|
||||
child: _getBody(context, enteColorScheme),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getBody(BuildContext context) {
|
||||
Widget _getBody(BuildContext context, EnteColorScheme colorScheme) {
|
||||
final hasLoggedIn = Configuration.instance.getToken() != null;
|
||||
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(
|
||||
|
@ -44,70 +52,57 @@ 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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
final sectionDivider = Divider(
|
||||
height: 20,
|
||||
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12),
|
||||
);
|
||||
contents.add(const Padding(padding: EdgeInsets.all(4)));
|
||||
const sectionSpacing = SizedBox(height: 8);
|
||||
contents.add(const SizedBox(height: 8));
|
||||
if (hasLoggedIn) {
|
||||
contents.addAll([
|
||||
const DetailsSectionWidget(),
|
||||
const Padding(padding: EdgeInsets.only(bottom: 24)),
|
||||
const SizedBox(height: 12),
|
||||
const BackupSectionWidget(),
|
||||
sectionDivider,
|
||||
sectionSpacing,
|
||||
const AccountSectionWidget(),
|
||||
sectionDivider,
|
||||
sectionSpacing,
|
||||
]);
|
||||
}
|
||||
contents.addAll([
|
||||
const SecuritySectionWidget(),
|
||||
sectionDivider,
|
||||
sectionSpacing,
|
||||
]);
|
||||
|
||||
if (Platform.isAndroid || kDebugMode) {
|
||||
contents.addAll([
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: const [
|
||||
SettingsSectionTitle("Theme"),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 4),
|
||||
child: ThemeSwitchWidget(),
|
||||
),
|
||||
],
|
||||
),
|
||||
sectionDivider,
|
||||
const ThemeSwitchWidget(),
|
||||
sectionSpacing,
|
||||
]);
|
||||
}
|
||||
|
||||
contents.addAll([
|
||||
const SupportSectionWidget(),
|
||||
sectionDivider,
|
||||
sectionSpacing,
|
||||
const SocialSectionWidget(),
|
||||
sectionDivider,
|
||||
const InfoSectionWidget(),
|
||||
sectionSpacing,
|
||||
const AboutSectionWidget(),
|
||||
]);
|
||||
if (hasLoggedIn) {
|
||||
contents.addAll([
|
||||
sectionDivider,
|
||||
sectionSpacing,
|
||||
const DangerSectionWidget(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (FeatureFlagService.instance.isInternalUserOrDebugBuild() &&
|
||||
hasLoggedIn) {
|
||||
contents.addAll([sectionDivider, const DebugSectionWidget()]);
|
||||
contents.addAll([sectionSpacing, const DebugSectionWidget()]);
|
||||
}
|
||||
contents.add(const AppVersionWidget());
|
||||
contents.add(
|
||||
|
@ -116,16 +111,24 @@ class SettingsPage extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 20),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 350),
|
||||
child: Column(
|
||||
children: contents,
|
||||
return UserDetailsStateWidget(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SettingsTitleBarWidget(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 428),
|
||||
child: Column(
|
||||
children: contents,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -49,6 +49,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
|
|||
Function() _selectedFilesListener;
|
||||
String _appBarTitle;
|
||||
final GlobalKey shareButtonKey = GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_selectedFilesListener = () {
|
||||
|
@ -125,6 +126,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 +186,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;
|
||||
}
|
||||
|
||||
|
|
46
pubspec.lock
46
pubspec.lock
|
@ -225,6 +225,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:
|
||||
|
@ -601,7 +608,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:
|
||||
|
@ -957,17 +985,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:
|
||||
|
|
10
pubspec.yaml
10
pubspec.yaml
|
@ -11,6 +11,7 @@ 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.42+372
|
||||
|
||||
environment:
|
||||
|
@ -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
|
||||
|
@ -68,7 +70,7 @@ 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
|
||||
|
@ -94,10 +96,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
|
||||
|
|
Loading…
Reference in a new issue