diff --git a/lib/ente_theme_data.dart b/lib/ente_theme_data.dart index 6dfcacada..6e05dbabd 100644 --- a/lib/ente_theme_data.dart +++ b/lib/ente_theme_data.dart @@ -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); diff --git a/lib/ui/components/captioned_text_widget.dart b/lib/ui/components/captioned_text_widget.dart new file mode 100644 index 000000000..8934e9e9f --- /dev/null +++ b/lib/ui/components/captioned_text_widget.dart @@ -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: ''), + ], + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/ui/components/expandable_menu_item_widget.dart b/lib/ui/components/expandable_menu_item_widget.dart new file mode 100644 index 000000000..89c448ada --- /dev/null +++ b/lib/ui/components/expandable_menu_item_widget.dart @@ -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 createState() => + _ExpandableMenuItemWidgetState(); +} + +class _ExpandableMenuItemWidgetState extends State { + 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, + ), + ); + } +} diff --git a/lib/ui/components/menu_item_widget.dart b/lib/ui/components/menu_item_widget.dart new file mode 100644 index 000000000..fb1451f9b --- /dev/null +++ b/lib/ui/components/menu_item_widget.dart @@ -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 createState() => _MenuItemWidgetState(); +} + +class _MenuItemWidgetState extends State { + @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; + } +} diff --git a/lib/ui/components/toggle_switch_widget.dart b/lib/ui/components/toggle_switch_widget.dart new file mode 100644 index 000000000..24d651cfe --- /dev/null +++ b/lib/ui/components/toggle_switch_widget.dart @@ -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 createState() => _ToggleSwitchWidgetState(); +} + +class _ToggleSwitchWidgetState extends State { + @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, + ), + ), + ), + ); + } +} diff --git a/lib/ui/settings/about_section_widget.dart b/lib/ui/settings/about_section_widget.dart new file mode 100644 index 000000000..bc30c017d --- /dev/null +++ b/lib/ui/settings/about_section_widget.dart @@ -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); + }, + ), + ); + }, + ); + } +} diff --git a/lib/ui/settings/account_section_widget.dart b/lib/ui/settings/account_section_widget.dart index b20ca3731..7f0d6cfb2 100644 --- a/lib/ui/settings/account_section_widget.dart +++ b/lib/ui/settings/account_section_widget.dart @@ -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 { @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 { 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 { ); } }, - 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 { ); } }, - 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 { ); } }, - child: const SettingsTextItem( - text: "Change password", - icon: Icons.navigate_next, - ), ), ], ); } - Future _getOrCreateRecoveryKey() async { + Future _getOrCreateRecoveryKey(BuildContext context) async { return Sodium.bin2hex( await UserService.instance.getOrCreateRecoveryKey(context), ); diff --git a/lib/ui/settings/backup_section_widget.dart b/lib/ui/settings/backup_section_widget.dart index ae8a02144..633612af1 100644 --- a/lib/ui/settings/backup_section_widget.dart +++ b/lib/ui/settings/backup_section_widget.dart @@ -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 { @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 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 { ), ); }, - 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 { "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 { } } }, - child: const SettingsTextItem( - text: "Deduplicate files", - icon: Icons.navigate_next, - ), ), + sectionOptionSpacing, ], ); return Column( diff --git a/lib/ui/settings/common_settings.dart b/lib/ui/settings/common_settings.dart index 3e10951b4..e85130600 100644 --- a/lib/ui/settings/common_settings.dart +++ b/lib/ui/settings/common_settings.dart @@ -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, ); } diff --git a/lib/ui/settings/danger_section_widget.dart b/lib/ui/settings/danger_section_widget.dart index 31d1c651d..1a33bd862 100644 --- a/lib/ui/settings/danger_section_widget.dart +++ b/lib/ui/settings/danger_section_widget.dart @@ -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 createState() => _DangerSectionWidgetState(); -} - -class _DangerSectionWidgetState extends State { @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 _onLogoutTapped() async { + Future _onLogoutTapped(BuildContext context) async { final AlertDialog alert = AlertDialog( title: const Text( "Logout", diff --git a/lib/ui/settings/debug_section_widget.dart b/lib/ui/settings/debug_section_widget.dart index ba093d826..0b5a209ec 100644 --- a/lib/ui/settings/debug_section_widget.dart +++ b/lib/ui/settings/debug_section_widget.dart @@ -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, ], ); } diff --git a/lib/ui/settings/details_section_widget.dart b/lib/ui/settings/details_section_widget.dart index 2f468120a..d94109d19 100644 --- a/lib/ui/settings/details_section_widget.dart +++ b/lib/ui/settings/details_section_widget.dart @@ -89,10 +89,8 @@ class _DetailsSectionWidgetState extends State { } 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( diff --git a/lib/ui/settings/info_section_widget.dart b/lib/ui/settings/info_section_widget.dart deleted file mode 100644 index 4ef4f954d..000000000 --- a/lib/ui/settings/info_section_widget.dart +++ /dev/null @@ -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(), - ], - ); - } -} diff --git a/lib/ui/settings/security_section_widget.dart b/lib/ui/settings/security_section_widget.dart index 374586681..be9005424 100644 --- a/lib/ui/settings/security_section_widget.dart +++ b/lib/ui/settings/security_section_widget.dart @@ -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 { @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 { 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 { } } }, - ); - } 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 { ); } }, - child: const SettingsTextItem( - text: "Active sessions", - icon: Icons.navigate_next, - ), ), + sectionOptionSpacing, ]); return Column( children: children, diff --git a/lib/ui/settings/social_section_widget.dart b/lib/ui/settings/social_section_widget.dart index 62baf3a9a..54cfc3b29 100644 --- a/lib/ui/settings/social_section_widget.dart +++ b/lib/ui/settings/social_section_widget.dart @@ -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 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); + }, + ); + } +} diff --git a/lib/ui/settings/support_section_widget.dart b/lib/ui/settings/support_section_widget.dart index 845fc657b..bd87ab0fc 100644 --- a/lib/ui/settings/support_section_widget.dart +++ b/lib/ui/settings/support_section_widget.dart @@ -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, ], ); } diff --git a/lib/ui/settings/theme_switch_widget.dart b/lib/ui/settings/theme_switch_widget.dart index a3a3f807e..dff1d0889 100644 --- a/lib/ui/settings/theme_switch_widget.dart +++ b/lib/ui/settings/theme_switch_widget.dart @@ -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 { - 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 { ); } + @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 ?? ">"), ); } } diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart index 5c0811e61..6842ffe0b 100644 --- a/lib/ui/settings_page.dart +++ b/lib/ui/settings_page.dart @@ -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, ),