Merge pull request #552 from ente-io/redesign-settings-new

Redesign + use components for settings
This commit is contained in:
Manav 2022-10-07 10:45:22 +05:30 committed by GitHub
commit e2fb00af59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 850 additions and 634 deletions

View file

@ -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);

View 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: ''),
],
),
),
)
],
),
),
);
}
}

View file

@ -0,0 +1,60 @@
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: ExpandablePanel(
header: MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: widget.title,
makeTextBold: true,
),
isHeaderOfExpansion: true,
leadingIcon: widget.leadingIcon,
trailingIcon: Icons.expand_more,
menuItemColor: enteColorScheme.fillFaint,
expandableController: expandableController,
),
collapsed: const SizedBox.shrink(),
expanded: widget.selectionOptionsWidget,
theme: getExpandableTheme(context),
controller: expandableController,
),
);
}
}

View file

@ -0,0 +1,123 @@
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;
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(widget.borderRadius),
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
? _isExpanded()
? const SizedBox.shrink()
: Icon(widget.trailingIcon)
: widget.trailingIcon != null
? Icon(
widget.trailingIcon,
color: widget.trailingIconIsMuted
? enteColorScheme.strokeMuted
: null,
)
: widget.trailingSwitch ?? const SizedBox.shrink(),
],
),
);
}
bool _isExpanded() {
return widget.expandableController!.value;
}
}

View file

@ -0,0 +1,38 @@
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,
),
),
),
);
}
}

View 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);
},
),
);
},
);
}
}

View file

@ -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,12 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
);
}
},
child: const SettingsTextItem(
text: "Change password",
icon: Icons.navigate_next,
),
),
],
);
}
Future<String> _getOrCreateRecoveryKey() async {
Future<String> _getOrCreateRecoveryKey(BuildContext context) async {
return Sodium.bin2hex(
await UserService.instance.getOrCreateRecoveryKey(context),
);

View file

@ -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(

View file

@ -1,23 +1,14 @@
// @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,
);
}

View file

@ -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",

View file

@ -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,
],
);
}

View file

@ -89,10 +89,8 @@ class _DetailsSectionWidgetState extends State<DetailsSectionWidget> {
}
Widget getContainer() {
return SizedBox(
width: 350,
height: 175,
// constraints: BoxConstraints(maxWidth: 390, maxHeight: 195),
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 428, maxHeight: 175),
child: Stack(
children: [
Container(

View file

@ -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(),
],
);
}
}

View file

@ -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,

View file

@ -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);
},
);
}
}

View file

@ -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,
],
);
}

View file

@ -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 ?? ">"),
);
}
}

View file

@ -5,16 +5,16 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/ente_theme_data.dart';
import 'package:photos/services/feature_flag_service.dart';
import 'package:photos/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/social_section_widget.dart';
import 'package:photos/ui/settings/support_section_widget.dart';
import 'package:photos/ui/settings/theme_switch_widget.dart';
@ -26,7 +26,14 @@ class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: _getBody(context),
body: Container(
color: Theme.of(context)
.colorScheme
.enteTheme
.colorScheme
.backgroundElevated,
child: _getBody(context),
),
);
}
@ -54,60 +61,47 @@ class SettingsPage extends StatelessWidget {
),
),
);
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(
@ -118,10 +112,10 @@ class SettingsPage extends StatelessWidget {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 20),
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 350),
constraints: const BoxConstraints(maxWidth: 428),
child: Column(
children: contents,
),