Переглянути джерело

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

Redesign + use components for settings
Manav 2 роки тому
батько
коміт
e2fb00af59

+ 0 - 4
lib/ente_theme_data.dart

@@ -341,10 +341,6 @@ extension CustomColorScheme on ColorScheme {
       ? const Color.fromRGBO(180, 180, 180, 1)
       ? const Color.fromRGBO(180, 180, 180, 1)
       : const Color.fromRGBO(100, 100, 100, 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
   Color get searchResultsColor => brightness == Brightness.light
       ? const Color.fromRGBO(245, 245, 245, 1.0)
       ? const Color.fromRGBO(245, 245, 245, 1.0)
       : const Color.fromRGBO(30, 30, 30, 1.0);
       : const Color.fromRGBO(30, 30, 30, 1.0);

+ 57 - 0
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: ''),
+                  ],
+                ),
+              ),
+            )
+          ],
+        ),
+      ),
+    );
+  }
+}

+ 60 - 0
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<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,
+      ),
+    );
+  }
+}

+ 123 - 0
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<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;
+  }
+}

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

+ 127 - 0
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);
+            },
+          ),
+        );
+      },
+    );
+  }
+}

+ 31 - 36
lib/ui/settings/account_section_widget.dart

@@ -1,6 +1,5 @@
 // @dart=2.9
 // @dart=2.9
 
 
-import 'package:expandable/expandable.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:photos/services/local_authentication_service.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/change_email_dialog.dart';
 import 'package:photos/ui/account/password_entry_page.dart';
 import 'package:photos/ui/account/password_entry_page.dart';
 import 'package:photos/ui/account/recovery_key_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/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/dialog_util.dart';
 import 'package:photos/utils/navigation_util.dart';
 import 'package:photos/utils/navigation_util.dart';
 
 
-class AccountSectionWidget extends StatefulWidget {
+class AccountSectionWidget extends StatelessWidget {
   const AccountSectionWidget({Key key}) : super(key: key);
   const AccountSectionWidget({Key key}) : super(key: key);
 
 
-  @override
-  AccountSectionWidgetState createState() => AccountSectionWidgetState();
-}
-
-class AccountSectionWidgetState extends State<AccountSectionWidget> {
   @override
   @override
   Widget build(BuildContext context) {
   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) {
   Column _getSectionOptions(BuildContext context) {
     return Column(
     return Column(
       children: [
       children: [
-        GestureDetector(
-          behavior: HitTestBehavior.translucent,
+        sectionOptionSpacing,
+        MenuItemWidget(
+          captionedTextWidget: const CaptionedTextWidget(
+            title: "Recovery key",
+          ),
+          trailingIcon: Icons.chevron_right_outlined,
+          trailingIconIsMuted: true,
           onTap: () async {
           onTap: () async {
             final hasAuthenticated = await LocalAuthenticationService.instance
             final hasAuthenticated = await LocalAuthenticationService.instance
                 .requestLocalAuthentication(
                 .requestLocalAuthentication(
@@ -46,7 +45,7 @@ class AccountSectionWidgetState extends State<AccountSectionWidget> {
             if (hasAuthenticated) {
             if (hasAuthenticated) {
               String recoveryKey;
               String recoveryKey;
               try {
               try {
-                recoveryKey = await _getOrCreateRecoveryKey();
+                recoveryKey = await _getOrCreateRecoveryKey(context);
               } catch (e) {
               } catch (e) {
                 showGenericErrorDialog(context);
                 showGenericErrorDialog(context);
                 return;
                 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 {
           onTap: () async {
             final hasAuthenticated = await LocalAuthenticationService.instance
             final hasAuthenticated = await LocalAuthenticationService.instance
                 .requestLocalAuthentication(
                 .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 {
           onTap: () async {
             final hasAuthenticated = await LocalAuthenticationService.instance
             final hasAuthenticated = await LocalAuthenticationService.instance
                 .requestLocalAuthentication(
                 .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(
     return Sodium.bin2hex(
       await UserService.instance.getOrCreateRecoveryKey(context),
       await UserService.instance.getOrCreateRecoveryKey(context),
     );
     );

+ 78 - 96
lib/ui/settings/backup_section_widget.dart

@@ -2,7 +2,6 @@
 
 
 import 'dart:io';
 import 'dart:io';
 
 
-import 'package:expandable/expandable.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/ente_theme_data.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/services/sync_service.dart';
 import 'package:photos/ui/backup_folder_selection_page.dart';
 import 'package:photos/ui/backup_folder_selection_page.dart';
 import 'package:photos/ui/common/dialogs.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/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/deduplicate_page.dart';
 import 'package:photos/ui/tools/free_space_page.dart';
 import 'package:photos/ui/tools/free_space_page.dart';
 import 'package:photos/utils/data_util.dart';
 import 'package:photos/utils/data_util.dart';
@@ -33,19 +34,23 @@ class BackupSectionWidget extends StatefulWidget {
 class BackupSectionWidgetState extends State<BackupSectionWidget> {
 class BackupSectionWidgetState extends State<BackupSectionWidget> {
   @override
   @override
   Widget build(BuildContext context) {
   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) {
   Widget _getSectionOptions(BuildContext context) {
     final List<Widget> sectionOptions = [
     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(
           routeToPage(
             context,
             context,
             const BackupFolderSelectionPage(
             const BackupFolderSelectionPage(
@@ -53,93 +58,72 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
             ),
             ),
           );
           );
         },
         },
-        child: const SettingsTextItem(
-          text: "Backed up folders",
-          icon: Icons.navigate_next,
-        ),
       ),
       ),
-      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(() {});
-              },
-            ),
-          ],
+      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 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) {
     if (Platform.isIOS) {
       sectionOptions.addAll([
       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(
     sectionOptions.addAll(
       [
       [
-        sectionOptionDivider,
-        GestureDetector(
-          behavior: HitTestBehavior.translucent,
+        MenuItemWidget(
+          captionedTextWidget: const CaptionedTextWidget(
+            title: "Free up space",
+          ),
+          trailingIcon: Icons.chevron_right_outlined,
+          trailingIconIsMuted: true,
           onTap: () async {
           onTap: () async {
             final dialog = createProgressDialog(context, "Calculating...");
             final dialog = createProgressDialog(context, "Calculating...");
             await dialog.show();
             await dialog.show();
@@ -160,20 +144,21 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
                 "You've no files on this device that can be deleted",
                 "You've no files on this device that can be deleted",
               );
               );
             } else {
             } else {
-              final bool result = await routeToPage(context, FreeSpacePage(status));
+              final bool result =
+                  await routeToPage(context, FreeSpacePage(status));
               if (result == true) {
               if (result == true) {
                 _showSpaceFreedDialog(status);
                 _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 {
           onTap: () async {
             final dialog = createProgressDialog(context, "Calculating...");
             final dialog = createProgressDialog(context, "Calculating...");
             await dialog.show();
             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(
     return Column(

+ 6 - 15
lib/ui/settings/common_settings.dart

@@ -1,23 +1,14 @@
-// @dart=2.9
-
-import 'dart:io';
-
 import 'package:expandable/expandable.dart';
 import 'package:expandable/expandable.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.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) {
 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,
   );
   );
 }
 }

+ 26 - 28
lib/ui/settings/danger_section_widget.dart

@@ -1,60 +1,58 @@
 // @dart=2.9
 // @dart=2.9
 
 
-import 'package:expandable/expandable.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/services/user_service.dart';
 import 'package:photos/services/user_service.dart';
 import 'package:photos/ui/account/delete_account_page.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/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';
 import 'package:photos/utils/navigation_util.dart';
 
 
-class DangerSectionWidget extends StatefulWidget {
+class DangerSectionWidget extends StatelessWidget {
   const DangerSectionWidget({Key key}) : super(key: key);
   const DangerSectionWidget({Key key}) : super(key: key);
 
 
-  @override
-  State<DangerSectionWidget> createState() => _DangerSectionWidgetState();
-}
-
-class _DangerSectionWidgetState extends State<DangerSectionWidget> {
   @override
   @override
   Widget build(BuildContext context) {
   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) {
   Widget _getSectionOptions(BuildContext context) {
     return Column(
     return Column(
       children: [
       children: [
-        GestureDetector(
-          behavior: HitTestBehavior.translucent,
+        sectionOptionSpacing,
+        MenuItemWidget(
+          captionedTextWidget: const CaptionedTextWidget(
+            title: "Logout",
+          ),
+          trailingIcon: Icons.chevron_right_outlined,
+          trailingIconIsMuted: true,
           onTap: () {
           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());
             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(
     final AlertDialog alert = AlertDialog(
       title: const Text(
       title: const Text(
         "Logout",
         "Logout",

+ 30 - 26
lib/ui/settings/debug_section_widget.dart

@@ -1,65 +1,69 @@
 // @dart=2.9
 // @dart=2.9
 
 
-import 'package:expandable/expandable.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/services/ignored_files_service.dart';
 import 'package:photos/services/ignored_files_service.dart';
 import 'package:photos/services/local_sync_service.dart';
 import 'package:photos/services/local_sync_service.dart';
 import 'package:photos/services/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/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';
 import 'package:photos/utils/toast_util.dart';
 
 
 class DebugSectionWidget extends StatelessWidget {
 class DebugSectionWidget extends StatelessWidget {
   const DebugSectionWidget({Key key}) : super(key: key);
   const DebugSectionWidget({Key key}) : super(key: key);
+
   @override
   @override
   Widget build(BuildContext context) {
   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) {
   Widget _getSectionOptions(BuildContext context) {
     return Column(
     return Column(
       children: [
       children: [
-        GestureDetector(
-          behavior: HitTestBehavior.translucent,
+        sectionOptionSpacing,
+        MenuItemWidget(
+          captionedTextWidget: const CaptionedTextWidget(
+            title: "Key attributes",
+          ),
+          trailingIcon: Icons.chevron_right_outlined,
+          trailingIconIsMuted: true,
           onTap: () async {
           onTap: () async {
             _showKeyAttributesDialog(context);
             _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 {
           onTap: () async {
             await LocalSyncService.instance.resetLocalSync();
             await LocalSyncService.instance.resetLocalSync();
             showToast(context, "Done");
             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 {
           onTap: () async {
             await IgnoredFilesService.instance.reset();
             await IgnoredFilesService.instance.reset();
             SyncService.instance.sync();
             SyncService.instance.sync();
             showToast(context, "Done");
             showToast(context, "Done");
           },
           },
-          child: const SettingsTextItem(
-            text: "Allow auto-upload for ignored files",
-            icon: Icons.navigate_next,
-          ),
         ),
         ),
+        sectionOptionSpacing,
       ],
       ],
     );
     );
   }
   }

+ 2 - 4
lib/ui/settings/details_section_widget.dart

@@ -89,10 +89,8 @@ class _DetailsSectionWidgetState extends State<DetailsSectionWidget> {
   }
   }
 
 
   Widget getContainer() {
   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(
       child: Stack(
         children: [
         children: [
           Container(
           Container(

+ 0 - 125
lib/ui/settings/info_section_widget.dart

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

+ 125 - 152
lib/ui/settings/security_section_widget.dart

@@ -3,7 +3,6 @@
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
 
 
-import 'package:expandable/expandable.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_windowmanager/flutter_windowmanager.dart';
 import 'package:flutter_windowmanager/flutter_windowmanager.dart';
 import 'package:photos/core/configuration.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/services/user_service.dart';
 import 'package:photos/ui/account/sessions_page.dart';
 import 'package:photos/ui/account/sessions_page.dart';
 import 'package:photos/ui/common/loading_widget.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/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 {
 class SecuritySectionWidget extends StatefulWidget {
   const SecuritySectionWidget({Key key}) : super(key: key);
   const SecuritySectionWidget({Key key}) : super(key: key);
@@ -49,11 +50,10 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   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()) {
     if (_config.hasConfiguredAccount()) {
       children.addAll(
       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,
                         value: snapshot.data,
                         onChanged: (value) async {
                         onChanged: (value) async {
                           final hasAuthenticated =
                           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([
     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) {
     if (Platform.isAndroid) {
       children.addAll(
       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([
     children.addAll([
-      sectionOptionDivider,
-      GestureDetector(
-        behavior: HitTestBehavior.translucent,
+      MenuItemWidget(
+        captionedTextWidget: const CaptionedTextWidget(
+          title: "Active sessions",
+        ),
+        trailingIcon: Icons.chevron_right_outlined,
+        trailingIconIsMuted: true,
         onTap: () async {
         onTap: () async {
           final hasAuthenticated = await LocalAuthenticationService.instance
           final hasAuthenticated = await LocalAuthenticationService.instance
               .requestLocalAuthentication(
               .requestLocalAuthentication(
@@ -258,11 +234,8 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
             );
             );
           }
           }
         },
         },
-        child: const SettingsTextItem(
-          text: "Active sessions",
-          icon: Icons.navigate_next,
-        ),
       ),
       ),
+      sectionOptionSpacing,
     ]);
     ]);
     return Column(
     return Column(
       children: children,
       children: children,

+ 42 - 53
lib/ui/settings/social_section_widget.dart

@@ -2,12 +2,12 @@
 
 
 import 'dart:io';
 import 'dart:io';
 
 
-import 'package:expandable/expandable.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/services/update_service.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/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';
 import 'package:url_launcher/url_launcher_string.dart';
 
 
 class SocialSectionWidget extends StatelessWidget {
 class SocialSectionWidget extends StatelessWidget {
@@ -15,68 +15,57 @@ class SocialSectionWidget extends StatelessWidget {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   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) {
   Widget _getSectionOptions(BuildContext context) {
     final List<Widget> options = [
     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()) {
     if (!UpdateService.instance.isIndependent()) {
       options.addAll(
       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);
     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);
+      },
+    );
+  }
+}

+ 29 - 26
lib/ui/settings/support_section_widget.dart

@@ -2,14 +2,14 @@
 
 
 import 'dart:io';
 import 'dart:io';
 
 
-import 'package:expandable/expandable.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/constants.dart';
 import 'package:photos/core/constants.dart';
 import 'package:photos/ui/common/web_page.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/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';
 import 'package:photos/utils/email_util.dart';
 
 
 class SupportSectionWidget extends StatelessWidget {
 class SupportSectionWidget extends StatelessWidget {
@@ -17,11 +17,10 @@ class SupportSectionWidget extends StatelessWidget {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   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";
         Platform.isAndroid ? "android-bugs@ente.io" : "ios-bugs@ente.io";
     return Column(
     return Column(
       children: [
       children: [
-        GestureDetector(
-          behavior: HitTestBehavior.translucent,
+        sectionOptionSpacing,
+        MenuItemWidget(
+          captionedTextWidget: const CaptionedTextWidget(
+            title: "Email",
+          ),
+          trailingIcon: Icons.chevron_right_outlined,
+          trailingIconIsMuted: true,
           onTap: () async {
           onTap: () async {
             await sendEmail(context, to: supportEmail);
             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: () {
           onTap: () {
             Navigator.of(context).push(
             Navigator.of(context).push(
               MaterialPageRoute(
               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 {
           onTap: () async {
             await sendLogs(context, "Report bug", bugsEmail);
             await sendLogs(context, "Report bug", bugsEmail);
           },
           },
@@ -71,11 +77,8 @@ class SupportSectionWidget extends StatelessWidget {
             final zipFilePath = await getZippedLogsFile(context);
             final zipFilePath = await getZippedLogsFile(context);
             await shareLogs(context, bugsEmail, zipFilePath);
             await shareLogs(context, bugsEmail, zipFilePath);
           },
           },
-          child: const SettingsTextItem(
-            text: "Report bug 🐞",
-            icon: Icons.navigate_next,
-          ),
         ),
         ),
+        sectionOptionSpacing,
       ],
       ],
     );
     );
   }
   }

+ 50 - 37
lib/ui/settings/theme_switch_widget.dart

@@ -1,8 +1,13 @@
 // @dart=2.9
 // @dart=2.9
 
 
 import 'package:adaptive_theme/adaptive_theme.dart';
 import 'package:adaptive_theme/adaptive_theme.dart';
-import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.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 {
 class ThemeSwitchWidget extends StatefulWidget {
   const ThemeSwitchWidget({Key key}) : super(key: key);
   const ThemeSwitchWidget({Key key}) : super(key: key);
@@ -12,13 +17,14 @@ class ThemeSwitchWidget extends StatefulWidget {
 }
 }
 
 
 class _ThemeSwitchWidgetState extends State<ThemeSwitchWidget> {
 class _ThemeSwitchWidgetState extends State<ThemeSwitchWidget> {
-  AdaptiveThemeMode themeMode;
+  AdaptiveThemeMode currentThemeMode;
+
   @override
   @override
   void initState() {
   void initState() {
     super.initState();
     super.initState();
     AdaptiveTheme.getThemeMode().then(
     AdaptiveTheme.getThemeMode().then(
       (value) {
       (value) {
-        themeMode = value ?? AdaptiveThemeMode.system;
+        currentThemeMode = value ?? AdaptiveThemeMode.system;
         debugPrint('theme value $value');
         debugPrint('theme value $value');
         if (mounted) {
         if (mounted) {
           setState(() => {});
           setState(() => {});
@@ -27,44 +33,51 @@ class _ThemeSwitchWidgetState extends State<ThemeSwitchWidget> {
     );
     );
   }
   }
 
 
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
   @override
   @override
   Widget build(BuildContext context) {
   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 {
       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 ?? ">"),
     );
     );
   }
   }
 }
 }

+ 25 - 31
lib/ui/settings_page.dart

@@ -5,16 +5,16 @@ import 'dart:io';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/core/configuration.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/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/account_section_widget.dart';
 import 'package:photos/ui/settings/app_version_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/backup_section_widget.dart';
 import 'package:photos/ui/settings/danger_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/debug_section_widget.dart';
 import 'package:photos/ui/settings/details_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/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/social_section_widget.dart';
 import 'package:photos/ui/settings/support_section_widget.dart';
 import 'package:photos/ui/settings/support_section_widget.dart';
 import 'package:photos/ui/settings/theme_switch_widget.dart';
 import 'package:photos/ui/settings/theme_switch_widget.dart';
@@ -26,7 +26,14 @@ class SettingsPage extends StatelessWidget {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return Scaffold(
     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) {
     if (hasLoggedIn) {
       contents.addAll([
       contents.addAll([
         const DetailsSectionWidget(),
         const DetailsSectionWidget(),
-        const Padding(padding: EdgeInsets.only(bottom: 24)),
+        const SizedBox(height: 12),
         const BackupSectionWidget(),
         const BackupSectionWidget(),
-        sectionDivider,
+        sectionSpacing,
         const AccountSectionWidget(),
         const AccountSectionWidget(),
-        sectionDivider,
+        sectionSpacing,
       ]);
       ]);
     }
     }
     contents.addAll([
     contents.addAll([
       const SecuritySectionWidget(),
       const SecuritySectionWidget(),
-      sectionDivider,
+      sectionSpacing,
     ]);
     ]);
 
 
     if (Platform.isAndroid || kDebugMode) {
     if (Platform.isAndroid || kDebugMode) {
       contents.addAll([
       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([
     contents.addAll([
       const SupportSectionWidget(),
       const SupportSectionWidget(),
-      sectionDivider,
+      sectionSpacing,
       const SocialSectionWidget(),
       const SocialSectionWidget(),
-      sectionDivider,
-      const InfoSectionWidget(),
+      sectionSpacing,
+      const AboutSectionWidget(),
     ]);
     ]);
     if (hasLoggedIn) {
     if (hasLoggedIn) {
       contents.addAll([
       contents.addAll([
-        sectionDivider,
+        sectionSpacing,
         const DangerSectionWidget(),
         const DangerSectionWidget(),
       ]);
       ]);
     }
     }
 
 
     if (FeatureFlagService.instance.isInternalUserOrDebugBuild() &&
     if (FeatureFlagService.instance.isInternalUserOrDebugBuild() &&
         hasLoggedIn) {
         hasLoggedIn) {
-      contents.addAll([sectionDivider, const DebugSectionWidget()]);
+      contents.addAll([sectionSpacing, const DebugSectionWidget()]);
     }
     }
     contents.add(const AppVersionWidget());
     contents.add(const AppVersionWidget());
     contents.add(
     contents.add(
@@ -118,10 +112,10 @@ class SettingsPage extends StatelessWidget {
 
 
     return SingleChildScrollView(
     return SingleChildScrollView(
       child: Padding(
       child: Padding(
-        padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 20),
+        padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
         child: Center(
         child: Center(
           child: ConstrainedBox(
           child: ConstrainedBox(
-            constraints: const BoxConstraints(maxWidth: 350),
+            constraints: const BoxConstraints(maxWidth: 428),
             child: Column(
             child: Column(
               children: contents,
               children: contents,
             ),
             ),