From 181325b3762032265756cf918172f9a5c2e9834d Mon Sep 17 00:00:00 2001 From: "n.hammami" Date: Wed, 7 May 2025 12:31:24 +0100 Subject: [PATCH 01/14] feat: add divider component in progress ... --- app/assets/il_components_divider.svg | 4 + app/assets/il_components_divider_dark.svg | 4 + .../gen/ouds_flutter_app_localizations.dart | 12 +++ .../ouds_flutter_app_localizations_ar.dart | 8 +- .../ouds_flutter_app_localizations_en.dart | 8 +- app/lib/l10n/ouds_flutter_en.arb | 6 +- app/lib/ui/components/components.dart | 7 ++ .../divider/divider_demo_screen.dart | 17 +++ .../lib/components/divider/ouds_divider.dart | 100 +++++++++++++++++- .../lib/components/lists/ouds_list_item.dart | 2 +- 10 files changed, 160 insertions(+), 8 deletions(-) create mode 100644 app/assets/il_components_divider.svg create mode 100644 app/assets/il_components_divider_dark.svg create mode 100644 app/lib/ui/components/divider/divider_demo_screen.dart diff --git a/app/assets/il_components_divider.svg b/app/assets/il_components_divider.svg new file mode 100644 index 00000000..f0310b2b --- /dev/null +++ b/app/assets/il_components_divider.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/il_components_divider_dark.svg b/app/assets/il_components_divider_dark.svg new file mode 100644 index 00000000..6b688093 --- /dev/null +++ b/app/assets/il_components_divider_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations.dart index a49f0034..a4721f78 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations.dart @@ -430,6 +430,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Open the app settings'** String get app_about_appSettings_label; + + /// No description provided for @app_components_divider_label. + /// + /// In en, this message translates to: + /// **'Divider'** + String get app_components_divider_label; + + /// No description provided for @app_components_divider_description_text. + /// + /// In en, this message translates to: + /// **'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'** + String get app_components_divider_description_text; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart index 17a4d64f..91d39428 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart @@ -1,5 +1,3 @@ -// ignore: unused_import -import 'package:intl/intl.dart' as intl; import 'ouds_flutter_app_localizations.dart'; // ignore_for_file: type=lint @@ -175,4 +173,10 @@ class AppLocalizationsAr extends AppLocalizations { @override String get app_about_appSettings_label => 'افتح إعدادات التطبيق'; + + @override + String get app_components_divider_label => 'Divider'; + + @override + String get app_components_divider_description_text => 'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'; } diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart index b2b6678a..d0bb5465 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart @@ -1,5 +1,3 @@ -// ignore: unused_import -import 'package:intl/intl.dart' as intl; import 'ouds_flutter_app_localizations.dart'; // ignore_for_file: type=lint @@ -175,4 +173,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get app_about_appSettings_label => 'Open the app settings'; + + @override + String get app_components_divider_label => 'Divider'; + + @override + String get app_components_divider_description_text => 'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'; } diff --git a/app/lib/l10n/ouds_flutter_en.arb b/app/lib/l10n/ouds_flutter_en.arb index cc0bfa67..83fec5a5 100644 --- a/app/lib/l10n/ouds_flutter_en.arb +++ b/app/lib/l10n/ouds_flutter_en.arb @@ -74,5 +74,9 @@ "app_about_legalInformation_label": "Legal information", "app_about_materialComponents_label": "Material 3 components", "app_about_changelog_label": "Changelog", - "app_about_appSettings_label": "Open the app settings" + "app_about_appSettings_label": "Open the app settings", + + "@_components_divider": {}, + "app_components_divider_label": "Divider", + "app_components_divider_description_text": "A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would." } diff --git a/app/lib/ui/components/components.dart b/app/lib/ui/components/components.dart index f18d922c..6f996166 100644 --- a/app/lib/ui/components/components.dart +++ b/app/lib/ui/components/components.dart @@ -16,6 +16,8 @@ import 'package:ouds_flutter_demo/ui/components/button/button_demo_screen.dart'; import 'package:ouds_flutter_demo/ui/components/component_entities.dart'; import 'package:ouds_flutter_demo/ui/utilities/adaptive_image_helper.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_demo_screen.dart'; + List components(BuildContext context) { return [ Component( @@ -24,5 +26,10 @@ List components(BuildContext context) { context.l10n.app_components_button_description_text, ButtonDemoScreen(), ), + Component( + context.l10n.app_components_divider_label, + AdaptiveImageHelper.getImage(context, 'assets/il_components_divider.svg'), + context.l10n.app_components_divider_description_text, + DividerDemoScreen()) ]; } diff --git a/app/lib/ui/components/divider/divider_demo_screen.dart b/app/lib/ui/components/divider/divider_demo_screen.dart new file mode 100644 index 00000000..73a12c43 --- /dev/null +++ b/app/lib/ui/components/divider/divider_demo_screen.dart @@ -0,0 +1,17 @@ + +import 'package:flutter/cupertino.dart'; + +class DividerDemoScreen extends StatefulWidget { + @override + State createState() => _DividerDemoScreenState(); + +} + +class _DividerDemoScreenState extends State { + @override + Widget build(BuildContext context) { + // TODO: implement build + throw UnimplementedError(); + } + +} \ No newline at end of file diff --git a/ouds_core/lib/components/divider/ouds_divider.dart b/ouds_core/lib/components/divider/ouds_divider.dart index c8989f55..07a29b1d 100644 --- a/ouds_core/lib/components/divider/ouds_divider.dart +++ b/ouds_core/lib/components/divider/ouds_divider.dart @@ -12,9 +12,105 @@ import 'package:flutter/material.dart'; -/// OUDS Divider is a styled thin line of 1dp thickness that groups content in lists and layouts. +import 'package:ouds_core/ouds_theme.dart'; + +/// TODO Add DSM link when available +/// An [OUDS Divider] component as per the design guidelines of OUDS. +/// +/// Dividers are used to visually structure an interface by clearly separating content sections. It helps to improve readability and content organization +/// without introducing a strong hierarchy like a heading or a container would. +/// +/// You can choose the orientation by using one of the named constructors: +/// - [OudsDivider.horizontal] +/// - [OudsDivider.vertical] +/// +/// The following parameters are available: +/// - [color] The color of the divider, chosen from the [OudsDividerColor] enum. +/// Default value set to [OudsDividerColor.defaultColor]. +/// - [length] The length of the divider (width for horizontal, height for vertical). +/// Defaults to [double.infinity]. +/// - [thickness] The thickness of the divider (height for horizontal, width for vertical). +/// Defaults to `1.0`. +/// - [margin] Optional margin around the divider, for spacing from surrounding content. +/// + +/// Represents the available colors for [OudsDivider]. + enum OudsDividerColor { + defaultColor, + muted, + emphasized, + brandPrimary, + onBrandPrimary, + alwaysBlack, + alwaysOnBlack, + alwaysWhite, + alwaysOnWhite, + } + class OudsDivider extends StatelessWidget { - const OudsDivider({super.key}); + final Axis orientation; + final OudsDividerColor color; + final double length; + final double thickness; + final EdgeInsetsGeometry? margin; + + /// Creates a horizontal divider. + const OudsDivider.horizontal({ + this.color = OudsDividerColor.defaultColor, + this.length = double.infinity, + this.thickness = 1, + this.margin, + }) : orientation = Axis.horizontal; + + /// Creates a vertical divider. + const OudsDivider.vertical({ + this.color = OudsDividerColor.defaultColor, + this.length = double.infinity, + this.thickness = 1, + this.margin, + }) : orientation = Axis.vertical; + + Color resolveColor(BuildContext context) { + final theme = OudsTheme.of(context); + switch (color) { + case OudsDividerColor.muted: + return theme.colorsScheme.borderMuted; + case OudsDividerColor.emphasized: + return theme.colorsScheme.borderEmphasized; + case OudsDividerColor.brandPrimary: + return theme.colorsScheme.borderBrandPrimary; + case OudsDividerColor.onBrandPrimary: + return theme.colorsScheme.borderOnBrandPrimary; + case OudsDividerColor.alwaysBlack: + return theme.colorsScheme.alwaysBlack; + case OudsDividerColor.alwaysOnBlack: + return theme.colorsScheme.alwaysOnBlack; + case OudsDividerColor.alwaysWhite: + return theme.colorsScheme.alwaysWhite; + case OudsDividerColor.alwaysOnWhite: + return theme.colorsScheme.alwaysOnWhite; + default: + return theme.colorsScheme.borderDefault; + } + } + + @override + Widget build(BuildContext context) { + final colorValue = resolveColor(context); + final divider = Container( + color: colorValue, + width: orientation == Axis.horizontal ? length : thickness, + height: orientation == Axis.horizontal ? thickness : length, + margin: margin, + ); + + return divider; + } +} + +/// OUDS Divider is a styled thin line of 1dp thickness that groups content in lists and layouts. +class OudsDividerList extends StatelessWidget { + const OudsDividerList({super.key}); final double _dividerOpacity = 0.12; diff --git a/ouds_core/lib/components/lists/ouds_list_item.dart b/ouds_core/lib/components/lists/ouds_list_item.dart index 1481cd74..a0f1f47c 100644 --- a/ouds_core/lib/components/lists/ouds_list_item.dart +++ b/ouds_core/lib/components/lists/ouds_list_item.dart @@ -129,7 +129,7 @@ class OudsListItem extends StatelessWidget { onTap: onPressed, ), ), - if (divider != null) const OudsDivider(), + if (divider != null) const OudsDividerList(), ], ), ), From 4945cae1493816d935fce8ed1e722456cf6ff1e8 Mon Sep 17 00:00:00 2001 From: "n.hammami" Date: Tue, 13 May 2025 11:02:46 +0100 Subject: [PATCH 02/14] feat: add divider component in progress ... --- .../gen/ouds_flutter_app_localizations.dart | 18 ++ .../ouds_flutter_app_localizations_ar.dart | 13 +- .../ouds_flutter_app_localizations_en.dart | 9 + app/lib/l10n/ouds_flutter_ar.arb | 9 +- app/lib/l10n/ouds_flutter_en.arb | 6 +- app/lib/ui/about/about_screen.dart | 7 +- app/lib/ui/components/component_entities.dart | 13 +- .../components/component_variants_screen.dart | 108 +++++++++++ app/lib/ui/components/components.dart | 13 +- app/lib/ui/components/components_screen.dart | 18 +- .../divider/divider_code_generator.dart | 36 ++++ .../divider/divider_customization.dart | 113 ++++++++++++ .../divider/divider_customization_utils.dart | 45 +++++ .../divider/divider_demo_screen.dart | 167 +++++++++++++++++- app/lib/ui/utilities/EnumExt.dart | 8 + .../customizable_dropdownmenu.dart | 120 +++++++++++++ .../ui/utilities/detail_screen_header.dart | 29 ++- .../lib/components/divider/ouds_divider.dart | 57 +++--- .../components/ouds_divider_tokens.dart | 11 +- 19 files changed, 748 insertions(+), 52 deletions(-) create mode 100644 app/lib/ui/components/component_variants_screen.dart create mode 100644 app/lib/ui/components/divider/divider_code_generator.dart create mode 100644 app/lib/ui/components/divider/divider_customization.dart create mode 100644 app/lib/ui/components/divider/divider_customization_utils.dart create mode 100644 app/lib/ui/utilities/EnumExt.dart create mode 100644 app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations.dart index a4721f78..2c2d36ce 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations.dart @@ -365,6 +365,12 @@ abstract class AppLocalizations { /// **'On colored background'** String get app_components_common_onColoredBackground_label; + /// No description provided for @app_components_common_color_label. + /// + /// In en, this message translates to: + /// **'Color'** + String get app_components_common_color_label; + /// No description provided for @app_components_button_label. /// /// In en, this message translates to: @@ -442,6 +448,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'** String get app_components_divider_description_text; + + /// No description provided for @app_components_divider_horizontalDivider_label. + /// + /// In en, this message translates to: + /// **'Horizontal divider'** + String get app_components_divider_horizontalDivider_label; + + /// No description provided for @app_components_divider_verticalDivider_label. + /// + /// In en, this message translates to: + /// **'Vertical divider'** + String get app_components_divider_verticalDivider_label; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart index 91d39428..ba1e95d4 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart @@ -141,6 +141,9 @@ class AppLocalizationsAr extends AppLocalizations { @override String get app_components_common_onColoredBackground_label => 'على خلفية ملوّنة'; + @override + String get app_components_common_color_label => 'اللون'; + @override String get app_components_button_label => 'زر'; @@ -175,8 +178,14 @@ class AppLocalizationsAr extends AppLocalizations { String get app_about_appSettings_label => 'افتح إعدادات التطبيق'; @override - String get app_components_divider_label => 'Divider'; + String get app_components_divider_label => 'فاصل'; + + @override + String get app_components_divider_description_text => 'الفاصل ينظم واجهة المستخدم بصريًا عن طريق فصل أقسام المحتوى بوضوح. يساعد على تحسين قابلية القراءة وتنظيم المحتوى دون إضافة تسلسل هرمي قوي كما في العناوين أو الحاويات.'; + + @override + String get app_components_divider_horizontalDivider_label => 'فاصل أفقي'; @override - String get app_components_divider_description_text => 'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'; + String get app_components_divider_verticalDivider_label => 'فاصل عمودي'; } diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart index d0bb5465..ea712d74 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart @@ -141,6 +141,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get app_components_common_onColoredBackground_label => 'On colored background'; + @override + String get app_components_common_color_label => 'Color'; + @override String get app_components_button_label => 'Button'; @@ -179,4 +182,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get app_components_divider_description_text => 'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'; + + @override + String get app_components_divider_horizontalDivider_label => 'Horizontal divider'; + + @override + String get app_components_divider_verticalDivider_label => 'Vertical divider'; } diff --git a/app/lib/l10n/ouds_flutter_ar.arb b/app/lib/l10n/ouds_flutter_ar.arb index 89d07b52..05791d52 100644 --- a/app/lib/l10n/ouds_flutter_ar.arb +++ b/app/lib/l10n/ouds_flutter_ar.arb @@ -60,6 +60,7 @@ "app_components_common_style_label": "النمط", "app_components_common_text_label": "نص", "app_components_common_onColoredBackground_label": "على خلفية ملوّنة", + "app_components_common_color_label": "اللون", "@_components_button": {}, "app_components_button_label": "زر", @@ -74,5 +75,11 @@ "app_about_legalInformation_label": "المعلومات القانونية", "app_about_materialComponents_label": "مكوّنات Material 3", "app_about_changelog_label": "سجل التغييرات", - "app_about_appSettings_label": "افتح إعدادات التطبيق" + "app_about_appSettings_label": "افتح إعدادات التطبيق", + + "@_components_divider": {}, + "app_components_divider_label": "فاصل", + "app_components_divider_description_text": "الفاصل ينظم واجهة المستخدم بصريًا عن طريق فصل أقسام المحتوى بوضوح. يساعد على تحسين قابلية القراءة وتنظيم المحتوى دون إضافة تسلسل هرمي قوي كما في العناوين أو الحاويات.", + "app_components_divider_horizontalDivider_label": "فاصل أفقي", + "app_components_divider_verticalDivider_label": "فاصل عمودي" } diff --git a/app/lib/l10n/ouds_flutter_en.arb b/app/lib/l10n/ouds_flutter_en.arb index 83fec5a5..5ee0b75f 100644 --- a/app/lib/l10n/ouds_flutter_en.arb +++ b/app/lib/l10n/ouds_flutter_en.arb @@ -60,6 +60,7 @@ "app_components_common_style_label": "Style", "app_components_common_text_label": "Text", "app_components_common_onColoredBackground_label": "On colored background", + "app_components_common_color_label": "Color", "@_components_button": {}, "app_components_button_label": "Button", @@ -78,5 +79,8 @@ "@_components_divider": {}, "app_components_divider_label": "Divider", - "app_components_divider_description_text": "A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would." + "app_components_divider_description_text": "A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.", + "app_components_divider_horizontalDivider_label": "Horizontal divider", + "app_components_divider_verticalDivider_label": "Vertical divider" + } diff --git a/app/lib/ui/about/about_screen.dart b/app/lib/ui/about/about_screen.dart index 41246ab8..57c1814b 100644 --- a/app/lib/ui/about/about_screen.dart +++ b/app/lib/ui/about/about_screen.dart @@ -162,7 +162,12 @@ class _AboutScreenState extends State { ), ListTile( title: Text(context.l10n.app_about_appSettings_label, - style: TextStyle(fontSize: currentTheme.fontTokens.sizeBodyLargeMobile, fontWeight: currentTheme.fontTokens.weightDefault, color: currentTheme.colorsScheme.contentBrandPrimary)), + style: TextStyle( + fontSize: currentTheme.fontTokens.sizeBodyLargeMobile, + fontWeight: currentTheme.fontTokens.weightDefault, + color: currentTheme.colorsScheme.contentBrandPrimary + ) + ), onTap: () { SettingsHelper.openAppropriateSettings(); }), diff --git a/app/lib/ui/components/component_entities.dart b/app/lib/ui/components/component_entities.dart index c760edb0..7ad81fb3 100644 --- a/app/lib/ui/components/component_entities.dart +++ b/app/lib/ui/components/component_entities.dart @@ -18,7 +18,18 @@ class Component { String title; String imageResourceName; String description; - Widget screen; + Widget? screen; + List? variants; + Component(this.title, this.imageResourceName, this.description, this.screen); + + Component.withVariant(this.title, this.imageResourceName, this.description, this.variants); } + +class VariantComponent { + String title; + Widget screen; + + VariantComponent(this.title, this.screen); +} \ No newline at end of file diff --git a/app/lib/ui/components/component_variants_screen.dart b/app/lib/ui/components/component_variants_screen.dart new file mode 100644 index 00000000..6a6cb351 --- /dev/null +++ b/app/lib/ui/components/component_variants_screen.dart @@ -0,0 +1,108 @@ +// +// Software Name: OUDS Flutter +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Software description: Flutter library of reusable graphical components +// + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ouds_flutter_demo/main_app_bar.dart'; +import 'package:ouds_flutter_demo/ui/components/component_entities.dart'; +import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; +import 'package:ouds_flutter_demo/ui/utilities/adaptive_image_helper.dart'; +import 'package:ouds_flutter_demo/ui/utilities/display_image.dart'; +import 'package:provider/provider.dart'; + +class ComponentVariantsScreen extends StatelessWidget { + final Component component; + + const ComponentVariantsScreen({super.key, required this.component}); + + @override + Widget build(BuildContext context) { + final themeController = Provider.of(context); + final currentTheme = themeController.currentTheme; + + return Scaffold( + appBar: MainAppBar( + title: component.title, + ), + body: SingleChildScrollView( + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + displayImage( + AdaptiveImageHelper.getImage(context, component.imageResourceName), + MediaQuery.of(context).size.width, + ), + Padding( + padding: EdgeInsetsDirectional.all(currentTheme.spaceTokens.insetTall), + child: Text( + component.description, + style: TextStyle( + fontSize: currentTheme.fontTokens.sizeBodyLargeMobile, + fontWeight: currentTheme.fontTokens.weightDefault, + letterSpacing: currentTheme.fontTokens.letterSpacingBodyLargeMobile, + height: 1.5, + ), + ), + ), + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: component.variants?.length, + itemBuilder: (BuildContext context, int index) { + if (component.variants != null && index < component.variants!.length) { + return VariantEntry( + variant: component.variants![index], + ); + } else { + return Container(); + } + }, + ), + ], + ), + ), + ), + ); + } +} + +class VariantEntry extends StatelessWidget { + final VariantComponent variant; + + const VariantEntry({super.key, required this.variant}); + + @override + Widget build(BuildContext context) { + final themeController = Provider.of(context); + final currentTheme = themeController.currentTheme; + + return Semantics( + button: true, + onTap: () { + Get.to(variant.screen); + }, + child: ListTile( + title: Text( + variant.title, + style: TextStyle( + fontSize: currentTheme.fontTokens.sizeHeadingMediumMobile, + fontWeight: currentTheme.fontTokens.weightHeading, + ), + ), + onTap: () { + Get.to(variant.screen); + }, + ), + ); + } +} diff --git a/app/lib/ui/components/components.dart b/app/lib/ui/components/components.dart index 6f996166..e472baec 100644 --- a/app/lib/ui/components/components.dart +++ b/app/lib/ui/components/components.dart @@ -26,10 +26,19 @@ List components(BuildContext context) { context.l10n.app_components_button_description_text, ButtonDemoScreen(), ), - Component( + Component.withVariant( context.l10n.app_components_divider_label, AdaptiveImageHelper.getImage(context, 'assets/il_components_divider.svg'), context.l10n.app_components_divider_description_text, - DividerDemoScreen()) + [ + VariantComponent( + context.l10n.app_components_divider_horizontalDivider_label, + DividerDemoScreen(vertical: false), + ), + VariantComponent( + context.l10n.app_components_divider_verticalDivider_label, + DividerDemoScreen(vertical: true), + ), + ]) ]; } diff --git a/app/lib/ui/components/components_screen.dart b/app/lib/ui/components/components_screen.dart index b173bea2..8149cf26 100644 --- a/app/lib/ui/components/components_screen.dart +++ b/app/lib/ui/components/components_screen.dart @@ -18,6 +18,8 @@ import 'package:ouds_flutter_demo/ui/components/component_entities.dart'; import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; import 'package:provider/provider.dart'; +import 'package:ouds_flutter_demo/ui/components/component_variants_screen.dart'; + class ComponentsScreen extends StatelessWidget { final List oudsComponents; @@ -47,10 +49,18 @@ class ComponentsScreen extends StatelessWidget { contentScale: BoxFit.cover, ), onClick: () { - Get.to( - component.screen, - transition: Transition.rightToLeft, - ); + if (component.variants == null) { + Get.to( + component.screen, + transition: Transition.rightToLeft, + ); + } else { + Get.to( + ComponentVariantsScreen( + component: component, + ), + ); + } }, ), ], diff --git a/app/lib/ui/components/divider/divider_code_generator.dart b/app/lib/ui/components/divider/divider_code_generator.dart new file mode 100644 index 00000000..7f21f210 --- /dev/null +++ b/app/lib/ui/components/divider/divider_code_generator.dart @@ -0,0 +1,36 @@ +// +// Software Name: OUDS Flutter +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Software description: Flutter library of reusable graphical components +// + +import 'package:flutter/material.dart'; + +/// +/// The CheckboxCodeGenerator class is responsible for dynamically generating Flutter +/// code for the customization of a checkbox component. It leverages the checkbox's +/// customization state, specifically the enabled and error states, and generates +/// the corresponding code in string format, which can be used for rendering or +/// previewing the checkbox with the selected properties. +/// +class DividerCodeGenerator { + // Static method to generate the code based on divider customization state + static String updateCode(BuildContext context, bool vertical) { + + return """${functionName(vertical)}(\ncolor: \n)"""; + } + + + // Method to get the function name according to the orientation of divider + static String functionName(bool vertical) { + // Return the function name based on the orientation of divider + return "${vertical == true ? 'OudsVerticalDivider' : 'OudsHorizontalDivider'}"; + } + +} \ No newline at end of file diff --git a/app/lib/ui/components/divider/divider_customization.dart b/app/lib/ui/components/divider/divider_customization.dart new file mode 100644 index 00000000..70ed7c3e --- /dev/null +++ b/app/lib/ui/components/divider/divider_customization.dart @@ -0,0 +1,113 @@ + +import 'package:flutter/material.dart'; +import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; +import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_widget_state.dart'; + +/// Section for InheritedWidget to pass data down the widget tree +class _DividerCustomization extends InheritedWidget { + const _DividerCustomization({ + required super.child, + required this.data, + }); + + final DividerCustomizationState data; + + @override + bool updateShouldNotify(_DividerCustomization oldWidget) => true; +} + +/// Main Widget class for Divider customization +class DividerCustomization extends StatefulWidget { + const DividerCustomization({ + super.key, + required this.child, + }); + + final Widget child; + + @override + DividerCustomizationState createState() => DividerCustomizationState(); + + static DividerCustomizationState? of(BuildContext context) { + return (context.dependOnInheritedWidgetOfExactType<_DividerCustomization>())?.data; + } +} + +/// Divider customization state management +class DividerCustomizationState extends CustomizationWidgetState { + late final ColorState colorState; + + + @override + void initState() { + super.initState(); + colorState = ColorState(setState, onColoredBoxState); + } + + // Proxy getters and setters to expose state values directly + DividerEnumColor get selectedColor => colorState.selected; + set selectedColor(DividerEnumColor value) => colorState.selected = value; + + @override + Widget build(BuildContext context) { + return _DividerCustomization( + data: this, + child: widget.child, + ); + } +} + +/// Color State Management +class ColorState { + ColorState(this._setState, this.onColoredBoxState); + + final void Function(void Function()) _setState; + final OnColoredBoxState onColoredBoxState; + + List _color = [ + DividerEnumColor.alwaysOnWhite, + DividerEnumColor.alwaysWhite, + DividerEnumColor.alwaysOnBlack, + DividerEnumColor.alwaysBlack, + DividerEnumColor.brandPrimary, + DividerEnumColor.defaultColor, + DividerEnumColor.emphasized, + DividerEnumColor.muted, + DividerEnumColor.onBrandPrimary, + ]; + + DividerEnumColor _selectedColor = DividerEnumColor.defaultColor; + + List get list => _color; + set list(List newList) { + _setState(() { + _color = newList; + }); + } + + DividerEnumColor get selected => _selectedColor; + set selected(DividerEnumColor newValue) { + _setState(() { + _selectedColor = newValue; + }); + } + +} + +/// Represents the color of an OUDS divider. +enum DividerEnumColor { + defaultColor, + muted, + emphasized, + brandPrimary, + onBrandPrimary, + alwaysBlack, + alwaysOnBlack, + alwaysWhite, + alwaysOnWhite; + + static String enumName(BuildContext context) { + return context.l10n.app_components_common_color_label; + } +} + diff --git a/app/lib/ui/components/divider/divider_customization_utils.dart b/app/lib/ui/components/divider/divider_customization_utils.dart new file mode 100644 index 00000000..617c8ffd --- /dev/null +++ b/app/lib/ui/components/divider/divider_customization_utils.dart @@ -0,0 +1,45 @@ +// +// Software Name: OUDS Flutter +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Software description: Flutter library of reusable graphical components +// +import 'package:ouds_core/components/divider/ouds_divider.dart'; + +import 'divider_customization.dart'; + +/// Utility class to map divider customization options to corresponding OudsDivider attributes. +/// +/// This class provides static methods to convert customization enums into the appropriate +/// [OudsDivider] properties. It includes methods for determining the divider color, +/// based on the input enum values. These methods help in translating +/// user-selected options into code that is used for button customization and rendering. + +class DividerCustomizationUtils { + /// Maps the style enum to `OudsButtonStyle`. + static OudsDividerColor getColor(DividerEnumColor? color) { + switch (color) { + case DividerEnumColor.onBrandPrimary: + return OudsDividerColor.onBrandPrimary; + case DividerEnumColor.muted: + return OudsDividerColor.muted; + case DividerEnumColor.emphasized: + return OudsDividerColor.emphasized; + case DividerEnumColor.brandPrimary: + return OudsDividerColor.brandPrimary; + case DividerEnumColor.alwaysBlack: + return OudsDividerColor.alwaysBlack; + case DividerEnumColor.alwaysOnBlack: + return OudsDividerColor.alwaysOnBlack; + case DividerEnumColor.alwaysWhite: + return OudsDividerColor.alwaysOnWhite; + default: + return OudsDividerColor.defaultColor; + } + } +} diff --git a/app/lib/ui/components/divider/divider_demo_screen.dart b/app/lib/ui/components/divider/divider_demo_screen.dart index 73a12c43..1a41363d 100644 --- a/app/lib/ui/components/divider/divider_demo_screen.dart +++ b/app/lib/ui/components/divider/divider_demo_screen.dart @@ -1,7 +1,30 @@ import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:ouds_core/components/divider/ouds_divider.dart'; +import 'package:ouds_core/components/ouds_colored_box.dart'; +import 'package:ouds_core/components/sheets_bottom/ouds_sheets_bottom.dart'; +import 'package:ouds_core/ouds_theme.dart'; +import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_customization_utils.dart'; + +import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; +import 'package:ouds_flutter_demo/ui/utilities/EnumExt.dart'; +import 'package:provider/provider.dart'; + +import 'package:ouds_flutter_demo/ui/utilities/detail_screen_header.dart'; + +import 'package:ouds_flutter_demo/main_app_bar.dart'; +import 'package:ouds_flutter_demo/ui/utilities/code.dart'; +import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_dropdownmenu.dart'; +import 'divider_code_generator.dart'; +import 'divider_customization.dart'; class DividerDemoScreen extends StatefulWidget { + final bool vertical; + + const DividerDemoScreen({required this.vertical}); + @override State createState() => _DividerDemoScreenState(); @@ -10,8 +33,148 @@ class DividerDemoScreen extends StatefulWidget { class _DividerDemoScreenState extends State { @override Widget build(BuildContext context) { - // TODO: implement build - throw UnimplementedError(); + return Scaffold( + bottomSheet: OudsSheetsBottom( + //onExpansionChanged: _onExpansionChanged, + sheetContent: const _CustomizationContent(), + title: context.l10n.app_common_customize_label, + ), + // key: _scaffoldKey, + appBar: widget.vertical + ? MainAppBar(title: context.l10n.app_components_divider_verticalDivider_label) // Display IndeterminateCheckboxDemo if true + : MainAppBar(title: context.l10n.app_components_divider_horizontalDivider_label), + body: SafeArea( + child: ExcludeSemantics( + //excluding: !_isBottomSheetExpanded, + child: _Body(vertical: widget.vertical), + ), + ), + ); + } + +} + +class _CustomizationContent extends StatefulWidget { + const _CustomizationContent(); + + @override + State<_CustomizationContent> createState() => _CustomizationContentState(); + + +} + +/// This state class handles the customization options for the checkbox +class _CustomizationContentState extends State<_CustomizationContent> { + _CustomizationContentState(); + + late ValueNotifier dividerColor; + + @override + void initState() { + dividerColor = rememberDividerDemoState(); + } + + ValueNotifier rememberDividerDemoState( + [OudsDividerColor initial = OudsDividerColor.defaultColor]) { + return ValueNotifier(initial); + } + + @override + Widget build(BuildContext context) { + + final DividerCustomizationState? customizationState = DividerCustomization.of(context); + + var colors = OudsDividerColor.values.toList() + ..sort((color, colorNext) => color.formattedName.compareTo(colorNext.formattedName)); + + + return CustomizationDropdownMenu( + label: context.l10n.app_components_common_color_label, + itemLabels: colors.map((color) => color.formattedName).toList(), + selectedItemIndex: colors.indexOf(dividerColor.value), + selectedOption: customizationState?.selectedColor, + onSelectionChange: (value) { + setState(() { + customizationState?.selectedColor = value; + }); + }, + itemLeadingIcons: colors.map((color) { + return () => Container( + width: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, + height: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, + decoration: BoxDecoration( + color: color.getColor(context), + shape: BoxShape.rectangle, + ), + ); + }).toList(), + ); + } + +} + +class _Body extends StatefulWidget { + final bool vertical; + + const _Body({required this.vertical}); + + @override + State<_Body> createState() => _BodyState(); +} + +class _BodyState extends State<_Body> { + @override + Widget build(BuildContext context) { + ThemeController? themeController = Provider.of(context, listen: false); + return DetailScreenDescription( + widget: Column( + children: [ + _DividerDemo(vertical: widget.vertical), + SizedBox(height: themeController.currentTheme.spaceTokens.fixedTall), + Code( + code: DividerCodeGenerator.updateCode(context, widget.vertical), + ), + ], + ), description: '', + ); } +} +/// This widget is now a StatefulWidget for the divider demo. +/// +/// Component [DividerDemo] demonstrates the behavior and functionality of a divider. +class _DividerDemo extends StatefulWidget { + final bool vertical; + + const _DividerDemo({required this.vertical}); + + @override + State<_DividerDemo> createState() => _DividerDemoState(); +} + +class _DividerDemoState extends State<_DividerDemo> { + ThemeController? themeController; + Color? color ; + DividerCustomizationState? customizationState; + + + + @override + Widget build(BuildContext context) { + customizationState = DividerCustomization.of(context); + + // Adding post-frame callback to update theme based on customization state + WidgetsBinding.instance.addPostFrameCallback((_) { + themeController?.setOnColoredSurface(customizationState?.hasOnColoredBox); + }); + + return OudsColoredBox( + color: customizationState?.hasOnColoredBox == true ? OudsColoredBoxColor.brandPrimary : null, + child: widget.vertical ? OudsDivider.vertical( + color: DividerCustomizationUtils.getColor(DividerEnumColor.brandPrimary), + ) : OudsDivider.horizontal( + color: DividerCustomizationUtils.getColor(customizationState?.selectedColor) + ), + ); + } } \ No newline at end of file diff --git a/app/lib/ui/utilities/EnumExt.dart b/app/lib/ui/utilities/EnumExt.dart new file mode 100644 index 00000000..1787e565 --- /dev/null +++ b/app/lib/ui/utilities/EnumExt.dart @@ -0,0 +1,8 @@ + +extension FormattedName on Enum { + String get formattedName { + final words = name.split(RegExp(r'(?=\p{Lu})', unicode: true)); + final joined = words.map((w) => w.toLowerCase()).join(' ').trim(); + return joined[0].toUpperCase() + joined.substring(1); + } +} diff --git a/app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart b/app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart new file mode 100644 index 00000000..552df5ea --- /dev/null +++ b/app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:ouds_core/ouds_theme.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_customization.dart'; +import 'package:provider/provider.dart'; + +import '../../theme/theme_controller.dart'; + +class CustomizationDropdownMenu extends StatefulWidget { + final String label; + final List itemLabels; + final int selectedItemIndex; + final Function(T) onSelectionChange; + final List? itemLeadingIcons; + final T? selectedOption; + + const CustomizationDropdownMenu({ + super.key, + required this.label, + required this.itemLabels, + required this.selectedItemIndex, + required this.onSelectionChange, + required this.selectedOption, + this.itemLeadingIcons, + }); + + @override + State createState() => _CustomizationDropdownMenuState(); +} + +class _CustomizationDropdownMenuState extends State { + late int selectedIndex; + bool expanded = false; + + @override + void initState() { + super.initState(); + selectedIndex = widget.selectedItemIndex; + } + + @override + Widget build(BuildContext context) { + final themeController = Provider.of(context, listen: false); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsetsDirectional.symmetric(horizontal: 16), + child: Text( + widget.label, + style: TextStyle( + fontSize: themeController.currentTheme.fontTokens.sizeBodyLargeMobile, + fontWeight: themeController.currentTheme.fontTokens.weightLabelStrong, + letterSpacing: themeController.currentTheme.fontTokens.letterSpacingBodyLargeMobile, + ), + ), + ), + Padding( + padding: const EdgeInsetsDirectional.symmetric(horizontal: 16, vertical: 8), + child: DropdownMenu( + initialSelection: widget.itemLabels[selectedIndex], + expandedInsets: EdgeInsets.zero, + inputDecorationTheme: const InputDecorationTheme(isDense: true), + textStyle: TextStyle( + fontSize: OudsTheme.of(context).fontTokens.sizeBodyLargeMobile, + fontWeight: OudsTheme.of(context).fontTokens.weightLabelStrong, + letterSpacing: OudsTheme.of(context).fontTokens.letterSpacingBodyLargeMobile, + ), + onSelected: (value) { + if (value != null) { + final index = widget.itemLabels.indexOf(value); + setState(() { + selectedIndex = index; + widget.onSelectionChange(value); + }); + + } + }, + leadingIcon: widget.itemLeadingIcons != null + ? buildDropdownLeadingIcon(widget.itemLeadingIcons, selectedIndex) + : null, + dropdownMenuEntries: + List.generate + (widget.itemLabels.length, (index) { + + final label = widget.itemLabels[index]; + final iconBuilder = widget.itemLeadingIcons != null && index < widget.itemLeadingIcons!.length + ? widget.itemLeadingIcons![index] + : null; + return DropdownMenuEntry( + labelWidget: Text(label, + style: TextStyle( + fontSize: OudsTheme.of(context).fontTokens.sizeBodyLargeMobile, + fontWeight: OudsTheme.of(context).fontTokens.weightLabelStrong, + letterSpacing: OudsTheme.of(context).fontTokens.letterSpacingBodyLargeMobile, + ) + ), + value: label, + label: label, + leadingIcon: iconBuilder != null + ? iconBuilder() + : null, + ); + }), + ), + ), + ], + ); + } + + Widget? buildDropdownLeadingIcon(List? builders, int index) { + if (builders != null && index < builders.length) { + return Padding(padding: EdgeInsetsDirectional.all( + OudsTheme.of(context).spaceTokens.paddingInlineShort + ), + child: builders[index]()); + } + return null; + } +} diff --git a/app/lib/ui/utilities/detail_screen_header.dart b/app/lib/ui/utilities/detail_screen_header.dart index eef922c2..075a2e0b 100644 --- a/app/lib/ui/utilities/detail_screen_header.dart +++ b/app/lib/ui/utilities/detail_screen_header.dart @@ -15,31 +15,42 @@ import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; import 'package:provider/provider.dart'; class DetailScreenDescription extends StatelessWidget { - final String description; + final String? description; final Widget? widget; const DetailScreenDescription({ super.key, - required this.description, + this.description, this.widget, }); @override Widget build(BuildContext context) { ThemeController? themeController = Provider.of(context, listen: false); + final currentTheme = themeController.currentTheme; + return SingleChildScrollView( child: Padding( padding: const EdgeInsetsDirectional.only(bottom: 80.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: EdgeInsetsDirectional.all(themeController.currentTheme.spaceTokens.insetTall), - child: Align( - alignment: AlignmentDirectional.centerStart, - child: Text(description), - ) - ), + if (description != null) + Padding( + padding: EdgeInsetsDirectional.all(themeController.currentTheme.spaceTokens.insetTall), + child: Align( + alignment: AlignmentDirectional.centerStart, + child: Text( + description!, + style: TextStyle( + fontSize: currentTheme.fontTokens.sizeBodyLargeMobile, + fontWeight: currentTheme.fontTokens.weightDefault, + letterSpacing: currentTheme.fontTokens.letterSpacingBodyLargeMobile, + height: 1.5, + ), + ), + ), + ), SizedBox(height: themeController.currentTheme.spaceTokens.fixedTall), if (widget != null) widget!, ], diff --git a/ouds_core/lib/components/divider/ouds_divider.dart b/ouds_core/lib/components/divider/ouds_divider.dart index 07a29b1d..16acb75a 100644 --- a/ouds_core/lib/components/divider/ouds_divider.dart +++ b/ouds_core/lib/components/divider/ouds_divider.dart @@ -44,7 +44,32 @@ import 'package:ouds_core/ouds_theme.dart'; alwaysBlack, alwaysOnBlack, alwaysWhite, - alwaysOnWhite, + alwaysOnWhite; + + Color getColor(BuildContext context) { + final theme = OudsTheme.of(context); + + switch (this) { + case OudsDividerColor.muted: + return theme.colorsScheme.borderMuted; + case OudsDividerColor.emphasized: + return theme.colorsScheme.borderEmphasized; + case OudsDividerColor.brandPrimary: + return theme.colorsScheme.borderBrandPrimary; + case OudsDividerColor.onBrandPrimary: + return theme.colorsScheme.borderOnBrandPrimary; + case OudsDividerColor.alwaysBlack: + return theme.colorsScheme.alwaysBlack; + case OudsDividerColor.alwaysOnBlack: + return theme.colorsScheme.alwaysOnBlack; + case OudsDividerColor.alwaysWhite: + return theme.colorsScheme.alwaysWhite; + case OudsDividerColor.alwaysOnWhite: + return theme.colorsScheme.alwaysOnWhite; + default: + return theme.colorsScheme.borderDefault; + } + } } class OudsDivider extends StatelessWidget { @@ -65,40 +90,18 @@ class OudsDivider extends StatelessWidget { /// Creates a vertical divider. const OudsDivider.vertical({ this.color = OudsDividerColor.defaultColor, - this.length = double.infinity, + this.length = 100, this.thickness = 1, this.margin, }) : orientation = Axis.vertical; - Color resolveColor(BuildContext context) { - final theme = OudsTheme.of(context); - switch (color) { - case OudsDividerColor.muted: - return theme.colorsScheme.borderMuted; - case OudsDividerColor.emphasized: - return theme.colorsScheme.borderEmphasized; - case OudsDividerColor.brandPrimary: - return theme.colorsScheme.borderBrandPrimary; - case OudsDividerColor.onBrandPrimary: - return theme.colorsScheme.borderOnBrandPrimary; - case OudsDividerColor.alwaysBlack: - return theme.colorsScheme.alwaysBlack; - case OudsDividerColor.alwaysOnBlack: - return theme.colorsScheme.alwaysOnBlack; - case OudsDividerColor.alwaysWhite: - return theme.colorsScheme.alwaysWhite; - case OudsDividerColor.alwaysOnWhite: - return theme.colorsScheme.alwaysOnWhite; - default: - return theme.colorsScheme.borderDefault; - } - } @override Widget build(BuildContext context) { - final colorValue = resolveColor(context); + var colors = OudsDividerColor.values; + final divider = Container( - color: colorValue, + color: colors.first.getColor(context), width: orientation == Axis.horizontal ? length : thickness, height: orientation == Axis.horizontal ? thickness : length, margin: margin, diff --git a/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart b/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart index d16cf15e..deaa5487 100644 --- a/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart +++ b/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart @@ -18,10 +18,17 @@ import 'package:ouds_theme_contract/ouds_tokens_provider.dart'; class OudsDividerTokens { final double borderWidth; + final double mediumSizeIconWithText; + final double smallSizeIconWithText; OudsDividerTokens({ required OudsProvidersTokens providersTokens, - double? borderWidth + double? borderWidth, + double? mediumSizeIconWithText, + double? smallSizeIconWithText }) : - borderWidth = borderWidth ?? providersTokens.borderTokens.widthThin; + borderWidth = borderWidth ?? providersTokens.borderTokens.widthThin, + mediumSizeIconWithText = mediumSizeIconWithText ?? providersTokens.sizeTokens.iconWithLabelMediumSizeLg, + smallSizeIconWithText = smallSizeIconWithText ?? providersTokens.sizeTokens.iconWithLabelSmallSizeLg; + } From 8e8aba9167910c0047007e7c499d352b1891f878 Mon Sep 17 00:00:00 2001 From: "n.hammami" Date: Wed, 14 May 2025 16:00:39 +0100 Subject: [PATCH 03/14] feat: add divider component using custom dropdown menu --- .../divider/divider_code_generator.dart | 22 ++-- .../divider/divider_customization.dart | 43 +++----- .../divider/divider_customization_utils.dart | 9 +- .../divider/divider_demo_screen.dart | 100 +++++++----------- .../ui/components/divider/divider_enum.dart | 27 +++++ app/lib/ui/utilities/EnumExt.dart | 8 -- ...u.dart => customizable_dropdown_menu.dart} | 97 +++++++---------- .../lib/components/divider/ouds_divider.dart | 84 +++++++-------- 8 files changed, 181 insertions(+), 209 deletions(-) create mode 100644 app/lib/ui/components/divider/divider_enum.dart delete mode 100644 app/lib/ui/utilities/EnumExt.dart rename app/lib/ui/utilities/customizable/{customizable_dropdownmenu.dart => customizable_dropdown_menu.dart} (51%) diff --git a/app/lib/ui/components/divider/divider_code_generator.dart b/app/lib/ui/components/divider/divider_code_generator.dart index 7f21f210..7374bb76 100644 --- a/app/lib/ui/components/divider/divider_code_generator.dart +++ b/app/lib/ui/components/divider/divider_code_generator.dart @@ -11,26 +11,32 @@ // import 'package:flutter/material.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/Divider_customization_utils.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_customization.dart'; /// /// The CheckboxCodeGenerator class is responsible for dynamically generating Flutter -/// code for the customization of a checkbox component. It leverages the checkbox's -/// customization state, specifically the enabled and error states, and generates +/// code for the customization of a divider component. It leverages the divider's +/// customization state, specifically the color state, and generates /// the corresponding code in string format, which can be used for rendering or -/// previewing the checkbox with the selected properties. +/// previewing the divider with the selected properties. /// class DividerCodeGenerator { // Static method to generate the code based on divider customization state static String updateCode(BuildContext context, bool vertical) { - - return """${functionName(vertical)}(\ncolor: \n)"""; + return """${functionName(vertical)}(\n${color(context)}\n)"""; } - // Method to get the function name according to the orientation of divider static String functionName(bool vertical) { // Return the function name based on the orientation of divider - return "${vertical == true ? 'OudsVerticalDivider' : 'OudsHorizontalDivider'}"; + return "${vertical == true ? 'OudsVerticalDivider' : 'OudsHorizontalDivider'}"; } -} \ No newline at end of file + // Method to generate the selected color + static String color(BuildContext context) { + final DividerCustomizationState? customizationState = DividerCustomization.of(context); + + return "color = ${DividerCustomizationUtils.getOudsDividerColor(customizationState?.selectedColor)}"; + } +} diff --git a/app/lib/ui/components/divider/divider_customization.dart b/app/lib/ui/components/divider/divider_customization.dart index 70ed7c3e..f2a43209 100644 --- a/app/lib/ui/components/divider/divider_customization.dart +++ b/app/lib/ui/components/divider/divider_customization.dart @@ -1,6 +1,5 @@ - import 'package:flutter/material.dart'; -import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_enum.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_widget_state.dart'; /// Section for InheritedWidget to pass data down the widget tree @@ -37,16 +36,18 @@ class DividerCustomization extends StatefulWidget { class DividerCustomizationState extends CustomizationWidgetState { late final ColorState colorState; - @override void initState() { super.initState(); - colorState = ColorState(setState, onColoredBoxState); + colorState = ColorState(setState); } // Proxy getters and setters to expose state values directly - DividerEnumColor get selectedColor => colorState.selected; - set selectedColor(DividerEnumColor value) => colorState.selected = value; + DividerEnumColor get selectedColor => colorState.selectedColor; + set selectedColor(DividerEnumColor value) => colorState.selectedColor = value; + + int get selectedIndex => colorState.index; + set selectedIndex(int value) => colorState.index = value; @override Widget build(BuildContext context) { @@ -59,10 +60,9 @@ class DividerCustomizationState extends CustomizationWidgetState _color = [ DividerEnumColor.alwaysOnWhite, @@ -77,6 +77,7 @@ class ColorState { ]; DividerEnumColor _selectedColor = DividerEnumColor.defaultColor; + int _selectedIndex = 5; List get list => _color; set list(List newList) { @@ -85,29 +86,17 @@ class ColorState { }); } - DividerEnumColor get selected => _selectedColor; - set selected(DividerEnumColor newValue) { + DividerEnumColor get selectedColor => _selectedColor; + set selectedColor(DividerEnumColor newValue) { _setState(() { _selectedColor = newValue; }); } -} - -/// Represents the color of an OUDS divider. -enum DividerEnumColor { - defaultColor, - muted, - emphasized, - brandPrimary, - onBrandPrimary, - alwaysBlack, - alwaysOnBlack, - alwaysWhite, - alwaysOnWhite; - - static String enumName(BuildContext context) { - return context.l10n.app_components_common_color_label; + int get index => _selectedIndex; + set index(int newValue) { + _setState(() { + _selectedIndex = newValue; + }); } } - diff --git a/app/lib/ui/components/divider/divider_customization_utils.dart b/app/lib/ui/components/divider/divider_customization_utils.dart index 617c8ffd..d08df990 100644 --- a/app/lib/ui/components/divider/divider_customization_utils.dart +++ b/app/lib/ui/components/divider/divider_customization_utils.dart @@ -10,8 +10,7 @@ // Software description: Flutter library of reusable graphical components // import 'package:ouds_core/components/divider/ouds_divider.dart'; - -import 'divider_customization.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_enum.dart'; /// Utility class to map divider customization options to corresponding OudsDivider attributes. /// @@ -21,8 +20,8 @@ import 'divider_customization.dart'; /// user-selected options into code that is used for button customization and rendering. class DividerCustomizationUtils { - /// Maps the style enum to `OudsButtonStyle`. - static OudsDividerColor getColor(DividerEnumColor? color) { + /// Maps the color enum to `OudsDividerColor`. + static OudsDividerColor getOudsDividerColor(DividerEnumColor? color) { switch (color) { case DividerEnumColor.onBrandPrimary: return OudsDividerColor.onBrandPrimary; @@ -37,6 +36,8 @@ class DividerCustomizationUtils { case DividerEnumColor.alwaysOnBlack: return OudsDividerColor.alwaysOnBlack; case DividerEnumColor.alwaysWhite: + return OudsDividerColor.alwaysWhite; + case DividerEnumColor.alwaysOnWhite: return OudsDividerColor.alwaysOnWhite; default: return OudsDividerColor.defaultColor; diff --git a/app/lib/ui/components/divider/divider_demo_screen.dart b/app/lib/ui/components/divider/divider_demo_screen.dart index 1a41363d..c915da8f 100644 --- a/app/lib/ui/components/divider/divider_demo_screen.dart +++ b/app/lib/ui/components/divider/divider_demo_screen.dart @@ -1,24 +1,19 @@ - -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:ouds_core/components/divider/ouds_divider.dart'; import 'package:ouds_core/components/ouds_colored_box.dart'; import 'package:ouds_core/components/sheets_bottom/ouds_sheets_bottom.dart'; import 'package:ouds_core/ouds_theme.dart'; import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; +import 'package:ouds_flutter_demo/main_app_bar.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_code_generator.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_customization.dart'; import 'package:ouds_flutter_demo/ui/components/divider/divider_customization_utils.dart'; - +import 'package:ouds_flutter_demo/ui/components/divider/divider_enum.dart'; import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; -import 'package:ouds_flutter_demo/ui/utilities/EnumExt.dart'; -import 'package:provider/provider.dart'; - -import 'package:ouds_flutter_demo/ui/utilities/detail_screen_header.dart'; - -import 'package:ouds_flutter_demo/main_app_bar.dart'; import 'package:ouds_flutter_demo/ui/utilities/code.dart'; -import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_dropdownmenu.dart'; -import 'divider_code_generator.dart'; -import 'divider_customization.dart'; +import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_dropdown_menu.dart'; +import 'package:ouds_flutter_demo/ui/utilities/detail_screen_header.dart'; +import 'package:provider/provider.dart'; class DividerDemoScreen extends StatefulWidget { final bool vertical; @@ -27,19 +22,18 @@ class DividerDemoScreen extends StatefulWidget { @override State createState() => _DividerDemoScreenState(); - } class _DividerDemoScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( + return DividerCustomization( + child: Scaffold( bottomSheet: OudsSheetsBottom( - //onExpansionChanged: _onExpansionChanged, sheetContent: const _CustomizationContent(), title: context.l10n.app_common_customize_label, ), - // key: _scaffoldKey, + // key: _scaffoldKey, appBar: widget.vertical ? MainAppBar(title: context.l10n.app_components_divider_verticalDivider_label) // Display IndeterminateCheckboxDemo if true : MainAppBar(title: context.l10n.app_components_divider_horizontalDivider_label), @@ -49,9 +43,8 @@ class _DividerDemoScreenState extends State { child: _Body(vertical: widget.vertical), ), ), - ); + )); } - } class _CustomizationContent extends StatefulWidget { @@ -59,58 +52,47 @@ class _CustomizationContent extends StatefulWidget { @override State<_CustomizationContent> createState() => _CustomizationContentState(); - - } /// This state class handles the customization options for the checkbox class _CustomizationContentState extends State<_CustomizationContent> { _CustomizationContentState(); - late ValueNotifier dividerColor; - - @override - void initState() { - dividerColor = rememberDividerDemoState(); - } - - ValueNotifier rememberDividerDemoState( - [OudsDividerColor initial = OudsDividerColor.defaultColor]) { + ValueNotifier rememberDividerDemoState([OudsDividerColor initial = OudsDividerColor.defaultColor]) { return ValueNotifier(initial); } @override Widget build(BuildContext context) { - final DividerCustomizationState? customizationState = DividerCustomization.of(context); - var colors = OudsDividerColor.values.toList() - ..sort((color, colorNext) => color.formattedName.compareTo(colorNext.formattedName)); - + //sort alphabetic order + var colors = customizationState!.colorState.list..sort((color, colorNext) => color.formattedName.compareTo(colorNext.formattedName)); return CustomizationDropdownMenu( - label: context.l10n.app_components_common_color_label, - itemLabels: colors.map((color) => color.formattedName).toList(), - selectedItemIndex: colors.indexOf(dividerColor.value), - selectedOption: customizationState?.selectedColor, - onSelectionChange: (value) { + label: DividerEnumColor.enumName(context), + options: colors, + selectedItemIndex: customizationState.selectedIndex, + selectedOption: customizationState.selectedColor, + getText: (option) => option.formattedName, + onSelectionChange: (value, index) { setState(() { - customizationState?.selectedColor = value; + customizationState.selectedColor = value; + customizationState.selectedIndex = index; }); }, - itemLeadingIcons: colors.map((color) { - return () => Container( - width: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, - height: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, - decoration: BoxDecoration( - color: color.getColor(context), - shape: BoxShape.rectangle, - ), - ); - }).toList(), + itemLeadingIcons: customizationState.colorState.list.map((color) { + return () => Container( + width: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, + height: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, + decoration: BoxDecoration( + color: DividerCustomizationUtils.getOudsDividerColor(color).getColor(context), + shape: BoxShape.rectangle, + ), + ); + }).toList(), ); } - } class _Body extends StatefulWidget { @@ -135,7 +117,7 @@ class _BodyState extends State<_Body> { code: DividerCodeGenerator.updateCode(context, widget.vertical), ), ], - ), description: '', + ), ); } } @@ -154,14 +136,12 @@ class _DividerDemo extends StatefulWidget { class _DividerDemoState extends State<_DividerDemo> { ThemeController? themeController; - Color? color ; DividerCustomizationState? customizationState; - - @override Widget build(BuildContext context) { customizationState = DividerCustomization.of(context); + themeController = Provider.of(context, listen: false); // Adding post-frame callback to update theme based on customization state WidgetsBinding.instance.addPostFrameCallback((_) { @@ -169,12 +149,10 @@ class _DividerDemoState extends State<_DividerDemo> { }); return OudsColoredBox( - color: customizationState?.hasOnColoredBox == true ? OudsColoredBoxColor.brandPrimary : null, - child: widget.vertical ? OudsDivider.vertical( - color: DividerCustomizationUtils.getColor(DividerEnumColor.brandPrimary), - ) : OudsDivider.horizontal( - color: DividerCustomizationUtils.getColor(customizationState?.selectedColor) - ), + color: customizationState?.hasOnColoredBox == true ? OudsColoredBoxColor.brandPrimary : OudsColoredBoxColor.statusNeutralMuted, + child: widget.vertical + ? OudsDivider.vertical(color: DividerCustomizationUtils.getOudsDividerColor(customizationState?.selectedColor)) + : OudsDivider.horizontal(color: DividerCustomizationUtils.getOudsDividerColor(customizationState?.selectedColor)), ); } -} \ No newline at end of file +} diff --git a/app/lib/ui/components/divider/divider_enum.dart b/app/lib/ui/components/divider/divider_enum.dart new file mode 100644 index 00000000..237f64f2 --- /dev/null +++ b/app/lib/ui/components/divider/divider_enum.dart @@ -0,0 +1,27 @@ +import 'package:flutter/cupertino.dart'; +import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; + +/// Represents the color of an OUDS divider. +enum DividerEnumColor { + defaultColor, + muted, + emphasized, + brandPrimary, + onBrandPrimary, + alwaysBlack, + alwaysOnBlack, + alwaysWhite, + alwaysOnWhite; + + static String enumName(BuildContext context) { + return context.l10n.app_components_common_color_label; + } +} + +extension FormattedName on Enum { + String get formattedName { + final words = name.split(RegExp(r'(?=\p{Lu})', unicode: true)); + final joined = words.map((w) => w.toLowerCase()).join(' ').trim(); + return joined[0].toUpperCase() + joined.substring(1); + } +} diff --git a/app/lib/ui/utilities/EnumExt.dart b/app/lib/ui/utilities/EnumExt.dart deleted file mode 100644 index 1787e565..00000000 --- a/app/lib/ui/utilities/EnumExt.dart +++ /dev/null @@ -1,8 +0,0 @@ - -extension FormattedName on Enum { - String get formattedName { - final words = name.split(RegExp(r'(?=\p{Lu})', unicode: true)); - final joined = words.map((w) => w.toLowerCase()).join(' ').trim(); - return joined[0].toUpperCase() + joined.substring(1); - } -} diff --git a/app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart b/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart similarity index 51% rename from app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart rename to app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart index 552df5ea..6b072db0 100644 --- a/app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart +++ b/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart @@ -1,53 +1,52 @@ +// +// Software Name: OUDS Flutter +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Software description: Flutter library of reusable graphical components +// import 'package:flutter/material.dart'; import 'package:ouds_core/ouds_theme.dart'; -import 'package:ouds_flutter_demo/ui/components/divider/divider_customization.dart'; import 'package:provider/provider.dart'; import '../../theme/theme_controller.dart'; -class CustomizationDropdownMenu extends StatefulWidget { +class CustomizationDropdownMenu extends StatelessWidget { final String label; - final List itemLabels; - final int selectedItemIndex; - final Function(T) onSelectionChange; + final List options; + int selectedItemIndex; + final String Function(T) getText; + final Function(T, int) onSelectionChange; final List? itemLeadingIcons; - final T? selectedOption; + T? selectedOption; - const CustomizationDropdownMenu({ + var isSelected = false; + + CustomizationDropdownMenu({ super.key, required this.label, - required this.itemLabels, + required this.options, required this.selectedItemIndex, required this.onSelectionChange, required this.selectedOption, this.itemLeadingIcons, + required this.getText, }); - @override - State createState() => _CustomizationDropdownMenuState(); -} - -class _CustomizationDropdownMenuState extends State { - late int selectedIndex; - bool expanded = false; - - @override - void initState() { - super.initState(); - selectedIndex = widget.selectedItemIndex; - } - @override Widget build(BuildContext context) { final themeController = Provider.of(context, listen: false); - return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsetsDirectional.symmetric(horizontal: 16), child: Text( - widget.label, + label, style: TextStyle( fontSize: themeController.currentTheme.fontTokens.sizeBodyLargeMobile, fontWeight: themeController.currentTheme.fontTokens.weightLabelStrong, @@ -57,8 +56,8 @@ class _CustomizationDropdownMenuState extends State { ), Padding( padding: const EdgeInsetsDirectional.symmetric(horizontal: 16, vertical: 8), - child: DropdownMenu( - initialSelection: widget.itemLabels[selectedIndex], + child: DropdownMenu( + initialSelection: options[selectedItemIndex], expandedInsets: EdgeInsets.zero, inputDecorationTheme: const InputDecorationTheme(isDense: true), textStyle: TextStyle( @@ -68,38 +67,25 @@ class _CustomizationDropdownMenuState extends State { ), onSelected: (value) { if (value != null) { - final index = widget.itemLabels.indexOf(value); - setState(() { - selectedIndex = index; - widget.onSelectionChange(value); - }); - + selectedItemIndex = options.indexOf(value); + onSelectionChange(value, selectedItemIndex); } }, - leadingIcon: widget.itemLeadingIcons != null - ? buildDropdownLeadingIcon(widget.itemLeadingIcons, selectedIndex) - : null, - dropdownMenuEntries: - List.generate - (widget.itemLabels.length, (index) { - - final label = widget.itemLabels[index]; - final iconBuilder = widget.itemLeadingIcons != null && index < widget.itemLeadingIcons!.length - ? widget.itemLeadingIcons![index] - : null; - return DropdownMenuEntry( - labelWidget: Text(label, + leadingIcon: itemLeadingIcons != null ? buildDropdownLeadingIcon(context, itemLeadingIcons, selectedItemIndex) : null, + dropdownMenuEntries: List.generate(options.length, (index) { + selectedOption = options[index]; + selectedItemIndex = index; + final iconBuilder = itemLeadingIcons != null && index < itemLeadingIcons!.length ? itemLeadingIcons![index] : null; + return DropdownMenuEntry( + labelWidget: Text(getText(options[index]), style: TextStyle( fontSize: OudsTheme.of(context).fontTokens.sizeBodyLargeMobile, fontWeight: OudsTheme.of(context).fontTokens.weightLabelStrong, letterSpacing: OudsTheme.of(context).fontTokens.letterSpacingBodyLargeMobile, - ) - ), - value: label, - label: label, - leadingIcon: iconBuilder != null - ? iconBuilder() - : null, + )), + value: selectedOption!, + label: getText(options[index]), + leadingIcon: iconBuilder != null ? iconBuilder() : null, ); }), ), @@ -108,12 +94,9 @@ class _CustomizationDropdownMenuState extends State { ); } - Widget? buildDropdownLeadingIcon(List? builders, int index) { + Widget? buildDropdownLeadingIcon(BuildContext context, List? builders, int index) { if (builders != null && index < builders.length) { - return Padding(padding: EdgeInsetsDirectional.all( - OudsTheme.of(context).spaceTokens.paddingInlineShort - ), - child: builders[index]()); + return Padding(padding: EdgeInsetsDirectional.all(OudsTheme.of(context).spaceTokens.paddingInlineShort), child: builders[index]()); } return null; } diff --git a/ouds_core/lib/components/divider/ouds_divider.dart b/ouds_core/lib/components/divider/ouds_divider.dart index 16acb75a..47b7b864 100644 --- a/ouds_core/lib/components/divider/ouds_divider.dart +++ b/ouds_core/lib/components/divider/ouds_divider.dart @@ -11,7 +11,6 @@ // import 'package:flutter/material.dart'; - import 'package:ouds_core/ouds_theme.dart'; /// TODO Add DSM link when available @@ -35,42 +34,42 @@ import 'package:ouds_core/ouds_theme.dart'; /// /// Represents the available colors for [OudsDivider]. - enum OudsDividerColor { - defaultColor, - muted, - emphasized, - brandPrimary, - onBrandPrimary, - alwaysBlack, - alwaysOnBlack, - alwaysWhite, - alwaysOnWhite; - - Color getColor(BuildContext context) { - final theme = OudsTheme.of(context); - - switch (this) { - case OudsDividerColor.muted: - return theme.colorsScheme.borderMuted; - case OudsDividerColor.emphasized: - return theme.colorsScheme.borderEmphasized; - case OudsDividerColor.brandPrimary: - return theme.colorsScheme.borderBrandPrimary; - case OudsDividerColor.onBrandPrimary: - return theme.colorsScheme.borderOnBrandPrimary; - case OudsDividerColor.alwaysBlack: - return theme.colorsScheme.alwaysBlack; - case OudsDividerColor.alwaysOnBlack: - return theme.colorsScheme.alwaysOnBlack; - case OudsDividerColor.alwaysWhite: - return theme.colorsScheme.alwaysWhite; - case OudsDividerColor.alwaysOnWhite: - return theme.colorsScheme.alwaysOnWhite; - default: - return theme.colorsScheme.borderDefault; +enum OudsDividerColor { + defaultColor, + muted, + emphasized, + brandPrimary, + onBrandPrimary, + alwaysBlack, + alwaysOnBlack, + alwaysWhite, + alwaysOnWhite; + + Color getColor(BuildContext context) { + final theme = OudsTheme.of(context); + + switch (this) { + case OudsDividerColor.muted: + return theme.colorsScheme.borderMuted; + case OudsDividerColor.emphasized: + return theme.colorsScheme.borderEmphasized; + case OudsDividerColor.brandPrimary: + return theme.colorsScheme.borderBrandPrimary; + case OudsDividerColor.onBrandPrimary: + return theme.colorsScheme.borderOnBrandPrimary; + case OudsDividerColor.alwaysBlack: + return theme.colorsScheme.alwaysBlack; + case OudsDividerColor.alwaysOnBlack: + return theme.colorsScheme.alwaysOnBlack; + case OudsDividerColor.alwaysWhite: + return theme.colorsScheme.alwaysWhite; + case OudsDividerColor.alwaysOnWhite: + return theme.colorsScheme.alwaysOnWhite; + default: + return theme.colorsScheme.borderDefault; } } - } +} class OudsDivider extends StatelessWidget { final Axis orientation; @@ -85,29 +84,26 @@ class OudsDivider extends StatelessWidget { this.length = double.infinity, this.thickness = 1, this.margin, - }) : orientation = Axis.horizontal; + }) : orientation = Axis.horizontal; /// Creates a vertical divider. const OudsDivider.vertical({ this.color = OudsDividerColor.defaultColor, - this.length = 100, + this.length = 0, this.thickness = 1, this.margin, - }) : orientation = Axis.vertical; - + }) : orientation = Axis.vertical; @override Widget build(BuildContext context) { - var colors = OudsDividerColor.values; - final divider = Container( - color: colors.first.getColor(context), + color: color.getColor(context), width: orientation == Axis.horizontal ? length : thickness, - height: orientation == Axis.horizontal ? thickness : length, + height: orientation == Axis.horizontal ? thickness : 50, margin: margin, ); - return divider; + return Padding(padding: EdgeInsetsDirectional.all(OudsTheme.of(context).spaceTokens.fixedMedium), child: divider); } } From 355f1a9398992f6b6594524e864559409ba952a5 Mon Sep 17 00:00:00 2001 From: "n.hammami" Date: Wed, 14 May 2025 16:21:00 +0100 Subject: [PATCH 04/14] update: update CHANGELOG.md in demo app and lib --- app/CHANGELOG.md | 3 +++ app/lib/ui/components/components.dart | 26 +++++++++++--------------- app/lib/ui/utilities/app_assets.dart | 12 ++++++++---- ouds_core/CHANGELOG.md | 3 +++ 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/app/CHANGELOG.md b/app/CHANGELOG.md index 3150f7a7..1043d97e 100644 --- a/app/CHANGELOG.md +++ b/app/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/Orange-OpenSource/ouds-flutter/compare/0.2.0...develop) +### Added +- [DemoApp][Library] Create component - `Divider` ([#57](https://github.com/Orange-OpenSource/ouds-flutter/issues/57)) + ### Changed - [DemoApp][Library] Refactor : create class global for icons names ([#152](https://github.com/Orange-OpenSource/ouds-flutter/issues/152)) diff --git a/app/lib/ui/components/components.dart b/app/lib/ui/components/components.dart index de233045..dd6bc304 100644 --- a/app/lib/ui/components/components.dart +++ b/app/lib/ui/components/components.dart @@ -16,11 +16,10 @@ import 'package:ouds_flutter_demo/ui/components/button/button_demo_screen.dart'; import 'package:ouds_flutter_demo/ui/components/checkbox/checkbox_demo_screen.dart'; import 'package:ouds_flutter_demo/ui/components/checkbox/checkbox_item_demo_screen.dart'; import 'package:ouds_flutter_demo/ui/components/component_entities.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_demo_screen.dart'; import 'package:ouds_flutter_demo/ui/utilities/adaptive_image_helper.dart'; import 'package:ouds_flutter_demo/ui/utilities/app_assets.dart'; -import 'package:ouds_flutter_demo/ui/components/divider/divider_demo_screen.dart'; - List components(BuildContext context) { return [ Component( @@ -53,18 +52,15 @@ List components(BuildContext context) { ], ), Component.withVariant( - context.l10n.app_components_divider_label, - AdaptiveImageHelper.getImage(context, 'assets/il_components_divider.svg'), - context.l10n.app_components_divider_description_text, - [ - VariantComponent( - context.l10n.app_components_divider_horizontalDivider_label, - DividerDemoScreen(vertical: false), - ), - VariantComponent( - context.l10n.app_components_divider_verticalDivider_label, - DividerDemoScreen(vertical: true), - ), - ]) + context.l10n.app_components_divider_label, AdaptiveImageHelper.getImage(context, AppAssets.images.ilComponentsDivider), context.l10n.app_components_divider_description_text, [ + VariantComponent( + context.l10n.app_components_divider_horizontalDivider_label, + DividerDemoScreen(vertical: false), + ), + VariantComponent( + context.l10n.app_components_divider_verticalDivider_label, + DividerDemoScreen(vertical: true), + ), + ]) ]; } diff --git a/app/lib/ui/utilities/app_assets.dart b/app/lib/ui/utilities/app_assets.dart index f5dc983c..7fba5f87 100644 --- a/app/lib/ui/utilities/app_assets.dart +++ b/app/lib/ui/utilities/app_assets.dart @@ -20,11 +20,15 @@ class AppAssets { } class _Images { - _Images(); + _Images(); + /// Components final String ilComponentsButton = 'assets/il_components_button.svg'; final String ilComponentsButtonDark = 'assets/il_components_button_dark.svg'; + final String ilComponentsDivider = 'assets/il_components_divider.svg'; + final String ilComponentsDividerDark = 'assets/il_components_divider_dark.svg'; + ///Tokens final String ilTokensColor = 'assets/il_tokens_color.svg'; final String ilTokensColorDark = 'assets/il_tokens_color_dark.svg'; @@ -35,9 +39,8 @@ class _Images { final String ilTokensOpacity = 'assets/il_tokens_opacity.svg'; final String ilTokensOpacityDark = 'assets/il_tokens_opacity_dark.svg'; - final String ilUnion = 'assets/il_union.svg'; - final String ilUnionDark = 'assets/il_union_dark.svg'; - + final String ilUnion = 'assets/il_union.svg'; + final String ilUnionDark = 'assets/il_union_dark.svg'; } class _Icons { @@ -49,6 +52,7 @@ class _Icons { final String icThemeSystem = 'assets/ic_theme_system.svg'; final String icToken = 'assets/ic_token.svg'; } + class _Fonts { const _Fonts(); } diff --git a/ouds_core/CHANGELOG.md b/ouds_core/CHANGELOG.md index fe3f4757..81651238 100644 --- a/ouds_core/CHANGELOG.md +++ b/ouds_core/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/Orange-OpenSource/ouds-flutter/compare/0.2.0...develop) +### Added +- [Library] Create component - Divider ([#57](https://github.com/Orange-OpenSource/ouds-flutter/issues/57)) + ## [0.2.0](https://github.com/Orange-OpenSource/ouds-flutter/compare/0.1.0...0.2.0) - 2025-05-13 ### Changed From 14ae14dc1fceec6a028075f9f986c54d43a06901 Mon Sep 17 00:00:00 2001 From: "n.hammami" Date: Thu, 15 May 2025 12:20:28 +0100 Subject: [PATCH 05/14] review: update files with recommended changes --- .../components/divider/divider_code_generator.dart | 2 +- .../ui/components/divider/divider_demo_screen.dart | 10 +++------- .../customizable/customizable_dropdown_menu.dart | 13 ++++++------- ouds_core/lib/components/divider/ouds_divider.dart | 14 +++++++++----- .../tokens/components/ouds_divider_tokens.dart | 14 +------------- 5 files changed, 20 insertions(+), 33 deletions(-) diff --git a/app/lib/ui/components/divider/divider_code_generator.dart b/app/lib/ui/components/divider/divider_code_generator.dart index 7374bb76..51dc12a8 100644 --- a/app/lib/ui/components/divider/divider_code_generator.dart +++ b/app/lib/ui/components/divider/divider_code_generator.dart @@ -30,7 +30,7 @@ class DividerCodeGenerator { // Method to get the function name according to the orientation of divider static String functionName(bool vertical) { // Return the function name based on the orientation of divider - return "${vertical == true ? 'OudsVerticalDivider' : 'OudsHorizontalDivider'}"; + return vertical == true ? 'OudsVerticalDivider' : 'OudsHorizontalDivider'; } // Method to generate the selected color diff --git a/app/lib/ui/components/divider/divider_demo_screen.dart b/app/lib/ui/components/divider/divider_demo_screen.dart index c915da8f..845d29be 100644 --- a/app/lib/ui/components/divider/divider_demo_screen.dart +++ b/app/lib/ui/components/divider/divider_demo_screen.dart @@ -18,7 +18,7 @@ import 'package:provider/provider.dart'; class DividerDemoScreen extends StatefulWidget { final bool vertical; - const DividerDemoScreen({required this.vertical}); + const DividerDemoScreen({super.key, required this.vertical}); @override State createState() => _DividerDemoScreenState(); @@ -58,10 +58,6 @@ class _CustomizationContent extends StatefulWidget { class _CustomizationContentState extends State<_CustomizationContent> { _CustomizationContentState(); - ValueNotifier rememberDividerDemoState([OudsDividerColor initial = OudsDividerColor.defaultColor]) { - return ValueNotifier(initial); - } - @override Widget build(BuildContext context) { final DividerCustomizationState? customizationState = DividerCustomization.of(context); @@ -83,8 +79,8 @@ class _CustomizationContentState extends State<_CustomizationContent> { }, itemLeadingIcons: customizationState.colorState.list.map((color) { return () => Container( - width: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, - height: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, + width: OudsTheme.of(context).spaceTokens.paddingBlockSpacious, + height: OudsTheme.of(context).spaceTokens.paddingBlockSpacious, decoration: BoxDecoration( color: DividerCustomizationUtils.getOudsDividerColor(color).getColor(context), shape: BoxShape.rectangle, diff --git a/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart b/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart index 6b072db0..e5483872 100644 --- a/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart +++ b/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart @@ -11,10 +11,9 @@ // import 'package:flutter/material.dart'; import 'package:ouds_core/ouds_theme.dart'; +import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; import 'package:provider/provider.dart'; -import '../../theme/theme_controller.dart'; - class CustomizationDropdownMenu extends StatelessWidget { final String label; final List options; @@ -40,11 +39,12 @@ class CustomizationDropdownMenu extends StatelessWidget { @override Widget build(BuildContext context) { final themeController = Provider.of(context, listen: false); + final currentTheme = themeController.currentTheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsetsDirectional.symmetric(horizontal: 16), + padding: EdgeInsetsDirectional.symmetric(horizontal: currentTheme.spaceTokens.fixedMedium), child: Text( label, style: TextStyle( @@ -55,7 +55,7 @@ class CustomizationDropdownMenu extends StatelessWidget { ), ), Padding( - padding: const EdgeInsetsDirectional.symmetric(horizontal: 16, vertical: 8), + padding: EdgeInsetsDirectional.symmetric(horizontal: currentTheme.spaceTokens.fixedMedium, vertical: currentTheme.spaceTokens.fixedShorter), child: DropdownMenu( initialSelection: options[selectedItemIndex], expandedInsets: EdgeInsets.zero, @@ -75,7 +75,6 @@ class CustomizationDropdownMenu extends StatelessWidget { dropdownMenuEntries: List.generate(options.length, (index) { selectedOption = options[index]; selectedItemIndex = index; - final iconBuilder = itemLeadingIcons != null && index < itemLeadingIcons!.length ? itemLeadingIcons![index] : null; return DropdownMenuEntry( labelWidget: Text(getText(options[index]), style: TextStyle( @@ -83,9 +82,9 @@ class CustomizationDropdownMenu extends StatelessWidget { fontWeight: OudsTheme.of(context).fontTokens.weightLabelStrong, letterSpacing: OudsTheme.of(context).fontTokens.letterSpacingBodyLargeMobile, )), - value: selectedOption!, + value: selectedOption as T, label: getText(options[index]), - leadingIcon: iconBuilder != null ? iconBuilder() : null, + leadingIcon: itemLeadingIcons != null ? buildDropdownLeadingIcon(context, itemLeadingIcons, selectedItemIndex) : null, ); }), ), diff --git a/ouds_core/lib/components/divider/ouds_divider.dart b/ouds_core/lib/components/divider/ouds_divider.dart index 47b7b864..100801f5 100644 --- a/ouds_core/lib/components/divider/ouds_divider.dart +++ b/ouds_core/lib/components/divider/ouds_divider.dart @@ -75,31 +75,35 @@ class OudsDivider extends StatelessWidget { final Axis orientation; final OudsDividerColor color; final double length; - final double thickness; + final double? thickness; final EdgeInsetsGeometry? margin; /// Creates a horizontal divider. const OudsDivider.horizontal({ + super.key, this.color = OudsDividerColor.defaultColor, this.length = double.infinity, - this.thickness = 1, + this.thickness, this.margin, }) : orientation = Axis.horizontal; /// Creates a vertical divider. const OudsDivider.vertical({ + super.key, this.color = OudsDividerColor.defaultColor, this.length = 0, - this.thickness = 1, + this.thickness, this.margin, }) : orientation = Axis.vertical; @override Widget build(BuildContext context) { + final actualThickness = thickness ?? OudsTheme.of(context).componentsTokens.divider.borderWidth; + final divider = Container( color: color.getColor(context), - width: orientation == Axis.horizontal ? length : thickness, - height: orientation == Axis.horizontal ? thickness : 50, + width: orientation == Axis.horizontal ? length : actualThickness, + height: orientation == Axis.horizontal ? actualThickness : 50, margin: margin, ); diff --git a/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart b/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart index deaa5487..0af4d9e5 100644 --- a/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart +++ b/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart @@ -13,22 +13,10 @@ // Tokens version 0.11.0 // Generated by Tokenator -import 'package:flutter/material.dart'; import 'package:ouds_theme_contract/ouds_tokens_provider.dart'; class OudsDividerTokens { final double borderWidth; - final double mediumSizeIconWithText; - final double smallSizeIconWithText; - - OudsDividerTokens({ - required OudsProvidersTokens providersTokens, - double? borderWidth, - double? mediumSizeIconWithText, - double? smallSizeIconWithText - }) : - borderWidth = borderWidth ?? providersTokens.borderTokens.widthThin, - mediumSizeIconWithText = mediumSizeIconWithText ?? providersTokens.sizeTokens.iconWithLabelMediumSizeLg, - smallSizeIconWithText = smallSizeIconWithText ?? providersTokens.sizeTokens.iconWithLabelSmallSizeLg; + OudsDividerTokens({required OudsProvidersTokens providersTokens, double? borderWidth}) : borderWidth = borderWidth ?? providersTokens.borderTokens.widthThin; } From 1b51eb20bf825f7d9e0cffe66b5bb47afec73c02 Mon Sep 17 00:00:00 2001 From: "n.hammami" Date: Wed, 7 May 2025 12:31:24 +0100 Subject: [PATCH 06/14] feat: add divider component in progress ... --- app/assets/il_components_divider.svg | 4 ++++ app/assets/il_components_divider_dark.svg | 4 ++++ .../gen/ouds_flutter_app_localizations.dart | 12 ++++++++++++ .../gen/ouds_flutter_app_localizations_ar.dart | 6 ++++++ .../gen/ouds_flutter_app_localizations_en.dart | 6 ++++++ app/lib/l10n/ouds_flutter_en.arb | 6 +++++- app/lib/ui/components/components.dart | 7 +++++++ .../components/divider/divider_demo_screen.dart | 17 +++++++++++++++++ 8 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 app/assets/il_components_divider.svg create mode 100644 app/assets/il_components_divider_dark.svg create mode 100644 app/lib/ui/components/divider/divider_demo_screen.dart diff --git a/app/assets/il_components_divider.svg b/app/assets/il_components_divider.svg new file mode 100644 index 00000000..f0310b2b --- /dev/null +++ b/app/assets/il_components_divider.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/il_components_divider_dark.svg b/app/assets/il_components_divider_dark.svg new file mode 100644 index 00000000..6b688093 --- /dev/null +++ b/app/assets/il_components_divider_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations.dart index ca850748..7433107e 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations.dart @@ -508,6 +508,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Open the app settings'** String get app_about_appSettings_label; + + /// No description provided for @app_components_divider_label. + /// + /// In en, this message translates to: + /// **'Divider'** + String get app_components_divider_label; + + /// No description provided for @app_components_divider_description_text. + /// + /// In en, this message translates to: + /// **'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'** + String get app_components_divider_description_text; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart index 0f2d867a..5ac9085c 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart @@ -212,4 +212,10 @@ class AppLocalizationsAr extends AppLocalizations { @override String get app_about_appSettings_label => 'افتح إعدادات التطبيق'; + + @override + String get app_components_divider_label => 'Divider'; + + @override + String get app_components_divider_description_text => 'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'; } diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart index 973dca9f..5e046344 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart @@ -212,4 +212,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get app_about_appSettings_label => 'Open the app settings'; + + @override + String get app_components_divider_label => 'Divider'; + + @override + String get app_components_divider_description_text => 'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'; } diff --git a/app/lib/l10n/ouds_flutter_en.arb b/app/lib/l10n/ouds_flutter_en.arb index ef80b422..140f9b48 100644 --- a/app/lib/l10n/ouds_flutter_en.arb +++ b/app/lib/l10n/ouds_flutter_en.arb @@ -91,5 +91,9 @@ "app_about_legalInformation_label": "Legal information", "app_about_materialComponents_label": "Material 3 components", "app_about_changelog_label": "Changelog", - "app_about_appSettings_label": "Open the app settings" + "app_about_appSettings_label": "Open the app settings", + + "@_components_divider": {}, + "app_components_divider_label": "Divider", + "app_components_divider_description_text": "A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would." } diff --git a/app/lib/ui/components/components.dart b/app/lib/ui/components/components.dart index c87f9516..6489e903 100644 --- a/app/lib/ui/components/components.dart +++ b/app/lib/ui/components/components.dart @@ -16,6 +16,7 @@ import 'package:ouds_flutter_demo/ui/components/button/button_demo_screen.dart'; import 'package:ouds_flutter_demo/ui/components/checkbox/checkbox_demo_screen.dart'; import 'package:ouds_flutter_demo/ui/components/checkbox/checkbox_item_demo_screen.dart'; import 'package:ouds_flutter_demo/ui/components/component_entities.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_demo_screen.dart'; import 'package:ouds_flutter_demo/ui/utilities/adaptive_image_helper.dart'; import 'package:ouds_flutter_demo/ui/utilities/app_assets.dart'; @@ -50,5 +51,11 @@ List components(BuildContext context) { ), ], ), + Component( + context.l10n.app_components_divider_label, + AdaptiveImageHelper.getImage(context, 'assets/il_components_divider.svg'), + context.l10n.app_components_divider_description_text, + DividerDemoScreen(), + ) ]; } diff --git a/app/lib/ui/components/divider/divider_demo_screen.dart b/app/lib/ui/components/divider/divider_demo_screen.dart new file mode 100644 index 00000000..73a12c43 --- /dev/null +++ b/app/lib/ui/components/divider/divider_demo_screen.dart @@ -0,0 +1,17 @@ + +import 'package:flutter/cupertino.dart'; + +class DividerDemoScreen extends StatefulWidget { + @override + State createState() => _DividerDemoScreenState(); + +} + +class _DividerDemoScreenState extends State { + @override + Widget build(BuildContext context) { + // TODO: implement build + throw UnimplementedError(); + } + +} \ No newline at end of file From 754d2bc2b84a589311843541e861042362b65b7c Mon Sep 17 00:00:00 2001 From: "n.hammami" Date: Tue, 13 May 2025 11:02:46 +0100 Subject: [PATCH 07/14] feat: add divider component in progress ... --- .../gen/ouds_flutter_app_localizations.dart | 18 ++ .../ouds_flutter_app_localizations_ar.dart | 13 +- .../ouds_flutter_app_localizations_en.dart | 9 + app/lib/l10n/ouds_flutter_ar.arb | 9 +- app/lib/l10n/ouds_flutter_en.arb | 6 +- app/lib/ui/about/about_screen.dart | 7 +- app/lib/ui/components/components.dart | 15 +- app/lib/ui/components/components_screen.dart | 2 + .../divider/divider_code_generator.dart | 36 ++++ .../divider/divider_customization.dart | 113 ++++++++++++ .../divider/divider_customization_utils.dart | 45 +++++ .../divider/divider_demo_screen.dart | 167 +++++++++++++++++- app/lib/ui/utilities/EnumExt.dart | 8 + .../customizable_dropdownmenu.dart | 120 +++++++++++++ .../lib/components/divider/ouds_divider.dart | 128 ++++++++++++++ .../components/ouds_divider_tokens.dart | 11 +- 16 files changed, 695 insertions(+), 12 deletions(-) create mode 100644 app/lib/ui/components/divider/divider_code_generator.dart create mode 100644 app/lib/ui/components/divider/divider_customization.dart create mode 100644 app/lib/ui/components/divider/divider_customization_utils.dart create mode 100644 app/lib/ui/utilities/EnumExt.dart create mode 100644 app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart create mode 100644 ouds_core/lib/components/divider/ouds_divider.dart diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations.dart index 7433107e..c5e7e59f 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations.dart @@ -371,6 +371,12 @@ abstract class AppLocalizations { /// **'On colored background'** String get app_components_common_onColoredBackground_label; + /// No description provided for @app_components_common_color_label. + /// + /// In en, this message translates to: + /// **'Color'** + String get app_components_common_color_label; + /// No description provided for @app_components_button_label. /// /// In en, this message translates to: @@ -520,6 +526,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'** String get app_components_divider_description_text; + + /// No description provided for @app_components_divider_horizontalDivider_label. + /// + /// In en, this message translates to: + /// **'Horizontal divider'** + String get app_components_divider_horizontalDivider_label; + + /// No description provided for @app_components_divider_verticalDivider_label. + /// + /// In en, this message translates to: + /// **'Vertical divider'** + String get app_components_divider_verticalDivider_label; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart index 5ac9085c..0c9af858 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations_ar.dart @@ -144,6 +144,9 @@ class AppLocalizationsAr extends AppLocalizations { @override String get app_components_common_onColoredBackground_label => 'على خلفية ملوّنة'; + @override + String get app_components_common_color_label => 'اللون'; + @override String get app_components_button_label => 'زر'; @@ -214,8 +217,14 @@ class AppLocalizationsAr extends AppLocalizations { String get app_about_appSettings_label => 'افتح إعدادات التطبيق'; @override - String get app_components_divider_label => 'Divider'; + String get app_components_divider_label => 'فاصل'; + + @override + String get app_components_divider_description_text => 'الفاصل ينظم واجهة المستخدم بصريًا عن طريق فصل أقسام المحتوى بوضوح. يساعد على تحسين قابلية القراءة وتنظيم المحتوى دون إضافة تسلسل هرمي قوي كما في العناوين أو الحاويات.'; + + @override + String get app_components_divider_horizontalDivider_label => 'فاصل أفقي'; @override - String get app_components_divider_description_text => 'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'; + String get app_components_divider_verticalDivider_label => 'فاصل عمودي'; } diff --git a/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart b/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart index 5e046344..c60176db 100644 --- a/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart +++ b/app/lib/l10n/gen/ouds_flutter_app_localizations_en.dart @@ -144,6 +144,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get app_components_common_onColoredBackground_label => 'On colored background'; + @override + String get app_components_common_color_label => 'Color'; + @override String get app_components_button_label => 'Button'; @@ -218,4 +221,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get app_components_divider_description_text => 'A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.'; + + @override + String get app_components_divider_horizontalDivider_label => 'Horizontal divider'; + + @override + String get app_components_divider_verticalDivider_label => 'Vertical divider'; } diff --git a/app/lib/l10n/ouds_flutter_ar.arb b/app/lib/l10n/ouds_flutter_ar.arb index f29473e7..386a3082 100644 --- a/app/lib/l10n/ouds_flutter_ar.arb +++ b/app/lib/l10n/ouds_flutter_ar.arb @@ -61,6 +61,7 @@ "app_components_common_style_label": "النمط", "app_components_common_text_label": "نص", "app_components_common_onColoredBackground_label": "على خلفية ملوّنة", + "app_components_common_color_label": "اللون", "@_components_button": {}, "app_components_button_label": "زر", @@ -91,5 +92,11 @@ "app_about_legalInformation_label": "المعلومات القانونية", "app_about_materialComponents_label": "مكوّنات Material 3", "app_about_changelog_label": "سجل التغييرات", - "app_about_appSettings_label": "افتح إعدادات التطبيق" + "app_about_appSettings_label": "افتح إعدادات التطبيق", + + "@_components_divider": {}, + "app_components_divider_label": "فاصل", + "app_components_divider_description_text": "الفاصل ينظم واجهة المستخدم بصريًا عن طريق فصل أقسام المحتوى بوضوح. يساعد على تحسين قابلية القراءة وتنظيم المحتوى دون إضافة تسلسل هرمي قوي كما في العناوين أو الحاويات.", + "app_components_divider_horizontalDivider_label": "فاصل أفقي", + "app_components_divider_verticalDivider_label": "فاصل عمودي" } diff --git a/app/lib/l10n/ouds_flutter_en.arb b/app/lib/l10n/ouds_flutter_en.arb index 140f9b48..3f69634b 100644 --- a/app/lib/l10n/ouds_flutter_en.arb +++ b/app/lib/l10n/ouds_flutter_en.arb @@ -61,6 +61,7 @@ "app_components_common_style_label": "Style", "app_components_common_text_label": "Text", "app_components_common_onColoredBackground_label": "On colored background", + "app_components_common_color_label": "Color", "@_components_button": {}, "app_components_button_label": "Button", @@ -95,5 +96,8 @@ "@_components_divider": {}, "app_components_divider_label": "Divider", - "app_components_divider_description_text": "A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would." + "app_components_divider_description_text": "A divider visually structures an interface by clearly separating content sections. It helps to improve readability and content organization without introducing a strong hierarchy like a heading or a container would.", + "app_components_divider_horizontalDivider_label": "Horizontal divider", + "app_components_divider_verticalDivider_label": "Vertical divider" + } diff --git a/app/lib/ui/about/about_screen.dart b/app/lib/ui/about/about_screen.dart index 41246ab8..57c1814b 100644 --- a/app/lib/ui/about/about_screen.dart +++ b/app/lib/ui/about/about_screen.dart @@ -162,7 +162,12 @@ class _AboutScreenState extends State { ), ListTile( title: Text(context.l10n.app_about_appSettings_label, - style: TextStyle(fontSize: currentTheme.fontTokens.sizeBodyLargeMobile, fontWeight: currentTheme.fontTokens.weightDefault, color: currentTheme.colorsScheme.contentBrandPrimary)), + style: TextStyle( + fontSize: currentTheme.fontTokens.sizeBodyLargeMobile, + fontWeight: currentTheme.fontTokens.weightDefault, + color: currentTheme.colorsScheme.contentBrandPrimary + ) + ), onTap: () { SettingsHelper.openAppropriateSettings(); }), diff --git a/app/lib/ui/components/components.dart b/app/lib/ui/components/components.dart index 6489e903..759b8807 100644 --- a/app/lib/ui/components/components.dart +++ b/app/lib/ui/components/components.dart @@ -51,11 +51,20 @@ List components(BuildContext context) { ), ], ), - Component( + Component.withVariant( context.l10n.app_components_divider_label, AdaptiveImageHelper.getImage(context, 'assets/il_components_divider.svg'), context.l10n.app_components_divider_description_text, - DividerDemoScreen(), - ) + [ + VariantComponent( + context.l10n.app_components_divider_horizontalDivider_label, + DividerDemoScreen(vertical: false), + ), + VariantComponent( + context.l10n.app_components_divider_verticalDivider_label, + DividerDemoScreen(vertical: true), + ), + ], + ), ]; } diff --git a/app/lib/ui/components/components_screen.dart b/app/lib/ui/components/components_screen.dart index b923a2ae..a796e3b4 100644 --- a/app/lib/ui/components/components_screen.dart +++ b/app/lib/ui/components/components_screen.dart @@ -19,6 +19,8 @@ import 'package:ouds_flutter_demo/ui/utilities/cards/ouds_cards_common.dart'; import 'package:ouds_flutter_demo/ui/utilities/cards/ouds_vertical_image_first_card.dart'; import 'package:provider/provider.dart'; +import 'package:ouds_flutter_demo/ui/components/component_variants_screen.dart'; + class ComponentsScreen extends StatelessWidget { final List oudsComponents; diff --git a/app/lib/ui/components/divider/divider_code_generator.dart b/app/lib/ui/components/divider/divider_code_generator.dart new file mode 100644 index 00000000..7f21f210 --- /dev/null +++ b/app/lib/ui/components/divider/divider_code_generator.dart @@ -0,0 +1,36 @@ +// +// Software Name: OUDS Flutter +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Software description: Flutter library of reusable graphical components +// + +import 'package:flutter/material.dart'; + +/// +/// The CheckboxCodeGenerator class is responsible for dynamically generating Flutter +/// code for the customization of a checkbox component. It leverages the checkbox's +/// customization state, specifically the enabled and error states, and generates +/// the corresponding code in string format, which can be used for rendering or +/// previewing the checkbox with the selected properties. +/// +class DividerCodeGenerator { + // Static method to generate the code based on divider customization state + static String updateCode(BuildContext context, bool vertical) { + + return """${functionName(vertical)}(\ncolor: \n)"""; + } + + + // Method to get the function name according to the orientation of divider + static String functionName(bool vertical) { + // Return the function name based on the orientation of divider + return "${vertical == true ? 'OudsVerticalDivider' : 'OudsHorizontalDivider'}"; + } + +} \ No newline at end of file diff --git a/app/lib/ui/components/divider/divider_customization.dart b/app/lib/ui/components/divider/divider_customization.dart new file mode 100644 index 00000000..70ed7c3e --- /dev/null +++ b/app/lib/ui/components/divider/divider_customization.dart @@ -0,0 +1,113 @@ + +import 'package:flutter/material.dart'; +import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; +import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_widget_state.dart'; + +/// Section for InheritedWidget to pass data down the widget tree +class _DividerCustomization extends InheritedWidget { + const _DividerCustomization({ + required super.child, + required this.data, + }); + + final DividerCustomizationState data; + + @override + bool updateShouldNotify(_DividerCustomization oldWidget) => true; +} + +/// Main Widget class for Divider customization +class DividerCustomization extends StatefulWidget { + const DividerCustomization({ + super.key, + required this.child, + }); + + final Widget child; + + @override + DividerCustomizationState createState() => DividerCustomizationState(); + + static DividerCustomizationState? of(BuildContext context) { + return (context.dependOnInheritedWidgetOfExactType<_DividerCustomization>())?.data; + } +} + +/// Divider customization state management +class DividerCustomizationState extends CustomizationWidgetState { + late final ColorState colorState; + + + @override + void initState() { + super.initState(); + colorState = ColorState(setState, onColoredBoxState); + } + + // Proxy getters and setters to expose state values directly + DividerEnumColor get selectedColor => colorState.selected; + set selectedColor(DividerEnumColor value) => colorState.selected = value; + + @override + Widget build(BuildContext context) { + return _DividerCustomization( + data: this, + child: widget.child, + ); + } +} + +/// Color State Management +class ColorState { + ColorState(this._setState, this.onColoredBoxState); + + final void Function(void Function()) _setState; + final OnColoredBoxState onColoredBoxState; + + List _color = [ + DividerEnumColor.alwaysOnWhite, + DividerEnumColor.alwaysWhite, + DividerEnumColor.alwaysOnBlack, + DividerEnumColor.alwaysBlack, + DividerEnumColor.brandPrimary, + DividerEnumColor.defaultColor, + DividerEnumColor.emphasized, + DividerEnumColor.muted, + DividerEnumColor.onBrandPrimary, + ]; + + DividerEnumColor _selectedColor = DividerEnumColor.defaultColor; + + List get list => _color; + set list(List newList) { + _setState(() { + _color = newList; + }); + } + + DividerEnumColor get selected => _selectedColor; + set selected(DividerEnumColor newValue) { + _setState(() { + _selectedColor = newValue; + }); + } + +} + +/// Represents the color of an OUDS divider. +enum DividerEnumColor { + defaultColor, + muted, + emphasized, + brandPrimary, + onBrandPrimary, + alwaysBlack, + alwaysOnBlack, + alwaysWhite, + alwaysOnWhite; + + static String enumName(BuildContext context) { + return context.l10n.app_components_common_color_label; + } +} + diff --git a/app/lib/ui/components/divider/divider_customization_utils.dart b/app/lib/ui/components/divider/divider_customization_utils.dart new file mode 100644 index 00000000..617c8ffd --- /dev/null +++ b/app/lib/ui/components/divider/divider_customization_utils.dart @@ -0,0 +1,45 @@ +// +// Software Name: OUDS Flutter +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Software description: Flutter library of reusable graphical components +// +import 'package:ouds_core/components/divider/ouds_divider.dart'; + +import 'divider_customization.dart'; + +/// Utility class to map divider customization options to corresponding OudsDivider attributes. +/// +/// This class provides static methods to convert customization enums into the appropriate +/// [OudsDivider] properties. It includes methods for determining the divider color, +/// based on the input enum values. These methods help in translating +/// user-selected options into code that is used for button customization and rendering. + +class DividerCustomizationUtils { + /// Maps the style enum to `OudsButtonStyle`. + static OudsDividerColor getColor(DividerEnumColor? color) { + switch (color) { + case DividerEnumColor.onBrandPrimary: + return OudsDividerColor.onBrandPrimary; + case DividerEnumColor.muted: + return OudsDividerColor.muted; + case DividerEnumColor.emphasized: + return OudsDividerColor.emphasized; + case DividerEnumColor.brandPrimary: + return OudsDividerColor.brandPrimary; + case DividerEnumColor.alwaysBlack: + return OudsDividerColor.alwaysBlack; + case DividerEnumColor.alwaysOnBlack: + return OudsDividerColor.alwaysOnBlack; + case DividerEnumColor.alwaysWhite: + return OudsDividerColor.alwaysOnWhite; + default: + return OudsDividerColor.defaultColor; + } + } +} diff --git a/app/lib/ui/components/divider/divider_demo_screen.dart b/app/lib/ui/components/divider/divider_demo_screen.dart index 73a12c43..1a41363d 100644 --- a/app/lib/ui/components/divider/divider_demo_screen.dart +++ b/app/lib/ui/components/divider/divider_demo_screen.dart @@ -1,7 +1,30 @@ import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:ouds_core/components/divider/ouds_divider.dart'; +import 'package:ouds_core/components/ouds_colored_box.dart'; +import 'package:ouds_core/components/sheets_bottom/ouds_sheets_bottom.dart'; +import 'package:ouds_core/ouds_theme.dart'; +import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_customization_utils.dart'; + +import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; +import 'package:ouds_flutter_demo/ui/utilities/EnumExt.dart'; +import 'package:provider/provider.dart'; + +import 'package:ouds_flutter_demo/ui/utilities/detail_screen_header.dart'; + +import 'package:ouds_flutter_demo/main_app_bar.dart'; +import 'package:ouds_flutter_demo/ui/utilities/code.dart'; +import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_dropdownmenu.dart'; +import 'divider_code_generator.dart'; +import 'divider_customization.dart'; class DividerDemoScreen extends StatefulWidget { + final bool vertical; + + const DividerDemoScreen({required this.vertical}); + @override State createState() => _DividerDemoScreenState(); @@ -10,8 +33,148 @@ class DividerDemoScreen extends StatefulWidget { class _DividerDemoScreenState extends State { @override Widget build(BuildContext context) { - // TODO: implement build - throw UnimplementedError(); + return Scaffold( + bottomSheet: OudsSheetsBottom( + //onExpansionChanged: _onExpansionChanged, + sheetContent: const _CustomizationContent(), + title: context.l10n.app_common_customize_label, + ), + // key: _scaffoldKey, + appBar: widget.vertical + ? MainAppBar(title: context.l10n.app_components_divider_verticalDivider_label) // Display IndeterminateCheckboxDemo if true + : MainAppBar(title: context.l10n.app_components_divider_horizontalDivider_label), + body: SafeArea( + child: ExcludeSemantics( + //excluding: !_isBottomSheetExpanded, + child: _Body(vertical: widget.vertical), + ), + ), + ); + } + +} + +class _CustomizationContent extends StatefulWidget { + const _CustomizationContent(); + + @override + State<_CustomizationContent> createState() => _CustomizationContentState(); + + +} + +/// This state class handles the customization options for the checkbox +class _CustomizationContentState extends State<_CustomizationContent> { + _CustomizationContentState(); + + late ValueNotifier dividerColor; + + @override + void initState() { + dividerColor = rememberDividerDemoState(); + } + + ValueNotifier rememberDividerDemoState( + [OudsDividerColor initial = OudsDividerColor.defaultColor]) { + return ValueNotifier(initial); + } + + @override + Widget build(BuildContext context) { + + final DividerCustomizationState? customizationState = DividerCustomization.of(context); + + var colors = OudsDividerColor.values.toList() + ..sort((color, colorNext) => color.formattedName.compareTo(colorNext.formattedName)); + + + return CustomizationDropdownMenu( + label: context.l10n.app_components_common_color_label, + itemLabels: colors.map((color) => color.formattedName).toList(), + selectedItemIndex: colors.indexOf(dividerColor.value), + selectedOption: customizationState?.selectedColor, + onSelectionChange: (value) { + setState(() { + customizationState?.selectedColor = value; + }); + }, + itemLeadingIcons: colors.map((color) { + return () => Container( + width: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, + height: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, + decoration: BoxDecoration( + color: color.getColor(context), + shape: BoxShape.rectangle, + ), + ); + }).toList(), + ); + } + +} + +class _Body extends StatefulWidget { + final bool vertical; + + const _Body({required this.vertical}); + + @override + State<_Body> createState() => _BodyState(); +} + +class _BodyState extends State<_Body> { + @override + Widget build(BuildContext context) { + ThemeController? themeController = Provider.of(context, listen: false); + return DetailScreenDescription( + widget: Column( + children: [ + _DividerDemo(vertical: widget.vertical), + SizedBox(height: themeController.currentTheme.spaceTokens.fixedTall), + Code( + code: DividerCodeGenerator.updateCode(context, widget.vertical), + ), + ], + ), description: '', + ); } +} +/// This widget is now a StatefulWidget for the divider demo. +/// +/// Component [DividerDemo] demonstrates the behavior and functionality of a divider. +class _DividerDemo extends StatefulWidget { + final bool vertical; + + const _DividerDemo({required this.vertical}); + + @override + State<_DividerDemo> createState() => _DividerDemoState(); +} + +class _DividerDemoState extends State<_DividerDemo> { + ThemeController? themeController; + Color? color ; + DividerCustomizationState? customizationState; + + + + @override + Widget build(BuildContext context) { + customizationState = DividerCustomization.of(context); + + // Adding post-frame callback to update theme based on customization state + WidgetsBinding.instance.addPostFrameCallback((_) { + themeController?.setOnColoredSurface(customizationState?.hasOnColoredBox); + }); + + return OudsColoredBox( + color: customizationState?.hasOnColoredBox == true ? OudsColoredBoxColor.brandPrimary : null, + child: widget.vertical ? OudsDivider.vertical( + color: DividerCustomizationUtils.getColor(DividerEnumColor.brandPrimary), + ) : OudsDivider.horizontal( + color: DividerCustomizationUtils.getColor(customizationState?.selectedColor) + ), + ); + } } \ No newline at end of file diff --git a/app/lib/ui/utilities/EnumExt.dart b/app/lib/ui/utilities/EnumExt.dart new file mode 100644 index 00000000..1787e565 --- /dev/null +++ b/app/lib/ui/utilities/EnumExt.dart @@ -0,0 +1,8 @@ + +extension FormattedName on Enum { + String get formattedName { + final words = name.split(RegExp(r'(?=\p{Lu})', unicode: true)); + final joined = words.map((w) => w.toLowerCase()).join(' ').trim(); + return joined[0].toUpperCase() + joined.substring(1); + } +} diff --git a/app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart b/app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart new file mode 100644 index 00000000..552df5ea --- /dev/null +++ b/app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:ouds_core/ouds_theme.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_customization.dart'; +import 'package:provider/provider.dart'; + +import '../../theme/theme_controller.dart'; + +class CustomizationDropdownMenu extends StatefulWidget { + final String label; + final List itemLabels; + final int selectedItemIndex; + final Function(T) onSelectionChange; + final List? itemLeadingIcons; + final T? selectedOption; + + const CustomizationDropdownMenu({ + super.key, + required this.label, + required this.itemLabels, + required this.selectedItemIndex, + required this.onSelectionChange, + required this.selectedOption, + this.itemLeadingIcons, + }); + + @override + State createState() => _CustomizationDropdownMenuState(); +} + +class _CustomizationDropdownMenuState extends State { + late int selectedIndex; + bool expanded = false; + + @override + void initState() { + super.initState(); + selectedIndex = widget.selectedItemIndex; + } + + @override + Widget build(BuildContext context) { + final themeController = Provider.of(context, listen: false); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsetsDirectional.symmetric(horizontal: 16), + child: Text( + widget.label, + style: TextStyle( + fontSize: themeController.currentTheme.fontTokens.sizeBodyLargeMobile, + fontWeight: themeController.currentTheme.fontTokens.weightLabelStrong, + letterSpacing: themeController.currentTheme.fontTokens.letterSpacingBodyLargeMobile, + ), + ), + ), + Padding( + padding: const EdgeInsetsDirectional.symmetric(horizontal: 16, vertical: 8), + child: DropdownMenu( + initialSelection: widget.itemLabels[selectedIndex], + expandedInsets: EdgeInsets.zero, + inputDecorationTheme: const InputDecorationTheme(isDense: true), + textStyle: TextStyle( + fontSize: OudsTheme.of(context).fontTokens.sizeBodyLargeMobile, + fontWeight: OudsTheme.of(context).fontTokens.weightLabelStrong, + letterSpacing: OudsTheme.of(context).fontTokens.letterSpacingBodyLargeMobile, + ), + onSelected: (value) { + if (value != null) { + final index = widget.itemLabels.indexOf(value); + setState(() { + selectedIndex = index; + widget.onSelectionChange(value); + }); + + } + }, + leadingIcon: widget.itemLeadingIcons != null + ? buildDropdownLeadingIcon(widget.itemLeadingIcons, selectedIndex) + : null, + dropdownMenuEntries: + List.generate + (widget.itemLabels.length, (index) { + + final label = widget.itemLabels[index]; + final iconBuilder = widget.itemLeadingIcons != null && index < widget.itemLeadingIcons!.length + ? widget.itemLeadingIcons![index] + : null; + return DropdownMenuEntry( + labelWidget: Text(label, + style: TextStyle( + fontSize: OudsTheme.of(context).fontTokens.sizeBodyLargeMobile, + fontWeight: OudsTheme.of(context).fontTokens.weightLabelStrong, + letterSpacing: OudsTheme.of(context).fontTokens.letterSpacingBodyLargeMobile, + ) + ), + value: label, + label: label, + leadingIcon: iconBuilder != null + ? iconBuilder() + : null, + ); + }), + ), + ), + ], + ); + } + + Widget? buildDropdownLeadingIcon(List? builders, int index) { + if (builders != null && index < builders.length) { + return Padding(padding: EdgeInsetsDirectional.all( + OudsTheme.of(context).spaceTokens.paddingInlineShort + ), + child: builders[index]()); + } + return null; + } +} diff --git a/ouds_core/lib/components/divider/ouds_divider.dart b/ouds_core/lib/components/divider/ouds_divider.dart new file mode 100644 index 00000000..16acb75a --- /dev/null +++ b/ouds_core/lib/components/divider/ouds_divider.dart @@ -0,0 +1,128 @@ +// +// Software Name: OUDS Flutter +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Software description: Flutter library of reusable graphical components +// + +import 'package:flutter/material.dart'; + +import 'package:ouds_core/ouds_theme.dart'; + +/// TODO Add DSM link when available +/// An [OUDS Divider] component as per the design guidelines of OUDS. +/// +/// Dividers are used to visually structure an interface by clearly separating content sections. It helps to improve readability and content organization +/// without introducing a strong hierarchy like a heading or a container would. +/// +/// You can choose the orientation by using one of the named constructors: +/// - [OudsDivider.horizontal] +/// - [OudsDivider.vertical] +/// +/// The following parameters are available: +/// - [color] The color of the divider, chosen from the [OudsDividerColor] enum. +/// Default value set to [OudsDividerColor.defaultColor]. +/// - [length] The length of the divider (width for horizontal, height for vertical). +/// Defaults to [double.infinity]. +/// - [thickness] The thickness of the divider (height for horizontal, width for vertical). +/// Defaults to `1.0`. +/// - [margin] Optional margin around the divider, for spacing from surrounding content. +/// + +/// Represents the available colors for [OudsDivider]. + enum OudsDividerColor { + defaultColor, + muted, + emphasized, + brandPrimary, + onBrandPrimary, + alwaysBlack, + alwaysOnBlack, + alwaysWhite, + alwaysOnWhite; + + Color getColor(BuildContext context) { + final theme = OudsTheme.of(context); + + switch (this) { + case OudsDividerColor.muted: + return theme.colorsScheme.borderMuted; + case OudsDividerColor.emphasized: + return theme.colorsScheme.borderEmphasized; + case OudsDividerColor.brandPrimary: + return theme.colorsScheme.borderBrandPrimary; + case OudsDividerColor.onBrandPrimary: + return theme.colorsScheme.borderOnBrandPrimary; + case OudsDividerColor.alwaysBlack: + return theme.colorsScheme.alwaysBlack; + case OudsDividerColor.alwaysOnBlack: + return theme.colorsScheme.alwaysOnBlack; + case OudsDividerColor.alwaysWhite: + return theme.colorsScheme.alwaysWhite; + case OudsDividerColor.alwaysOnWhite: + return theme.colorsScheme.alwaysOnWhite; + default: + return theme.colorsScheme.borderDefault; + } + } + } + +class OudsDivider extends StatelessWidget { + final Axis orientation; + final OudsDividerColor color; + final double length; + final double thickness; + final EdgeInsetsGeometry? margin; + + /// Creates a horizontal divider. + const OudsDivider.horizontal({ + this.color = OudsDividerColor.defaultColor, + this.length = double.infinity, + this.thickness = 1, + this.margin, + }) : orientation = Axis.horizontal; + + /// Creates a vertical divider. + const OudsDivider.vertical({ + this.color = OudsDividerColor.defaultColor, + this.length = 100, + this.thickness = 1, + this.margin, + }) : orientation = Axis.vertical; + + + @override + Widget build(BuildContext context) { + var colors = OudsDividerColor.values; + + final divider = Container( + color: colors.first.getColor(context), + width: orientation == Axis.horizontal ? length : thickness, + height: orientation == Axis.horizontal ? thickness : length, + margin: margin, + ); + + return divider; + } +} + +/// OUDS Divider is a styled thin line of 1dp thickness that groups content in lists and layouts. +class OudsDividerList extends StatelessWidget { + const OudsDividerList({super.key}); + + final double _dividerOpacity = 0.12; + + @override + Widget build(BuildContext context) { + return Divider( + height: 10, + thickness: 1, + color: Theme.of(context).colorScheme.onSurface.withOpacity(_dividerOpacity), + ); + } +} diff --git a/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart b/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart index d16cf15e..deaa5487 100644 --- a/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart +++ b/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart @@ -18,10 +18,17 @@ import 'package:ouds_theme_contract/ouds_tokens_provider.dart'; class OudsDividerTokens { final double borderWidth; + final double mediumSizeIconWithText; + final double smallSizeIconWithText; OudsDividerTokens({ required OudsProvidersTokens providersTokens, - double? borderWidth + double? borderWidth, + double? mediumSizeIconWithText, + double? smallSizeIconWithText }) : - borderWidth = borderWidth ?? providersTokens.borderTokens.widthThin; + borderWidth = borderWidth ?? providersTokens.borderTokens.widthThin, + mediumSizeIconWithText = mediumSizeIconWithText ?? providersTokens.sizeTokens.iconWithLabelMediumSizeLg, + smallSizeIconWithText = smallSizeIconWithText ?? providersTokens.sizeTokens.iconWithLabelSmallSizeLg; + } From 61c7e15758c155e5dfa4fecdf9e20829b0387f8b Mon Sep 17 00:00:00 2001 From: "n.hammami" Date: Wed, 14 May 2025 16:00:39 +0100 Subject: [PATCH 08/14] feat: add divider component using custom dropdown menu --- .../divider/divider_code_generator.dart | 22 ++-- .../divider/divider_customization.dart | 43 +++----- .../divider/divider_customization_utils.dart | 9 +- .../divider/divider_demo_screen.dart | 100 +++++++----------- .../ui/components/divider/divider_enum.dart | 27 +++++ app/lib/ui/utilities/EnumExt.dart | 8 -- ...u.dart => customizable_dropdown_menu.dart} | 97 +++++++---------- .../lib/components/divider/ouds_divider.dart | 84 +++++++-------- 8 files changed, 181 insertions(+), 209 deletions(-) create mode 100644 app/lib/ui/components/divider/divider_enum.dart delete mode 100644 app/lib/ui/utilities/EnumExt.dart rename app/lib/ui/utilities/customizable/{customizable_dropdownmenu.dart => customizable_dropdown_menu.dart} (51%) diff --git a/app/lib/ui/components/divider/divider_code_generator.dart b/app/lib/ui/components/divider/divider_code_generator.dart index 7f21f210..7374bb76 100644 --- a/app/lib/ui/components/divider/divider_code_generator.dart +++ b/app/lib/ui/components/divider/divider_code_generator.dart @@ -11,26 +11,32 @@ // import 'package:flutter/material.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/Divider_customization_utils.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_customization.dart'; /// /// The CheckboxCodeGenerator class is responsible for dynamically generating Flutter -/// code for the customization of a checkbox component. It leverages the checkbox's -/// customization state, specifically the enabled and error states, and generates +/// code for the customization of a divider component. It leverages the divider's +/// customization state, specifically the color state, and generates /// the corresponding code in string format, which can be used for rendering or -/// previewing the checkbox with the selected properties. +/// previewing the divider with the selected properties. /// class DividerCodeGenerator { // Static method to generate the code based on divider customization state static String updateCode(BuildContext context, bool vertical) { - - return """${functionName(vertical)}(\ncolor: \n)"""; + return """${functionName(vertical)}(\n${color(context)}\n)"""; } - // Method to get the function name according to the orientation of divider static String functionName(bool vertical) { // Return the function name based on the orientation of divider - return "${vertical == true ? 'OudsVerticalDivider' : 'OudsHorizontalDivider'}"; + return "${vertical == true ? 'OudsVerticalDivider' : 'OudsHorizontalDivider'}"; } -} \ No newline at end of file + // Method to generate the selected color + static String color(BuildContext context) { + final DividerCustomizationState? customizationState = DividerCustomization.of(context); + + return "color = ${DividerCustomizationUtils.getOudsDividerColor(customizationState?.selectedColor)}"; + } +} diff --git a/app/lib/ui/components/divider/divider_customization.dart b/app/lib/ui/components/divider/divider_customization.dart index 70ed7c3e..f2a43209 100644 --- a/app/lib/ui/components/divider/divider_customization.dart +++ b/app/lib/ui/components/divider/divider_customization.dart @@ -1,6 +1,5 @@ - import 'package:flutter/material.dart'; -import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_enum.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_widget_state.dart'; /// Section for InheritedWidget to pass data down the widget tree @@ -37,16 +36,18 @@ class DividerCustomization extends StatefulWidget { class DividerCustomizationState extends CustomizationWidgetState { late final ColorState colorState; - @override void initState() { super.initState(); - colorState = ColorState(setState, onColoredBoxState); + colorState = ColorState(setState); } // Proxy getters and setters to expose state values directly - DividerEnumColor get selectedColor => colorState.selected; - set selectedColor(DividerEnumColor value) => colorState.selected = value; + DividerEnumColor get selectedColor => colorState.selectedColor; + set selectedColor(DividerEnumColor value) => colorState.selectedColor = value; + + int get selectedIndex => colorState.index; + set selectedIndex(int value) => colorState.index = value; @override Widget build(BuildContext context) { @@ -59,10 +60,9 @@ class DividerCustomizationState extends CustomizationWidgetState _color = [ DividerEnumColor.alwaysOnWhite, @@ -77,6 +77,7 @@ class ColorState { ]; DividerEnumColor _selectedColor = DividerEnumColor.defaultColor; + int _selectedIndex = 5; List get list => _color; set list(List newList) { @@ -85,29 +86,17 @@ class ColorState { }); } - DividerEnumColor get selected => _selectedColor; - set selected(DividerEnumColor newValue) { + DividerEnumColor get selectedColor => _selectedColor; + set selectedColor(DividerEnumColor newValue) { _setState(() { _selectedColor = newValue; }); } -} - -/// Represents the color of an OUDS divider. -enum DividerEnumColor { - defaultColor, - muted, - emphasized, - brandPrimary, - onBrandPrimary, - alwaysBlack, - alwaysOnBlack, - alwaysWhite, - alwaysOnWhite; - - static String enumName(BuildContext context) { - return context.l10n.app_components_common_color_label; + int get index => _selectedIndex; + set index(int newValue) { + _setState(() { + _selectedIndex = newValue; + }); } } - diff --git a/app/lib/ui/components/divider/divider_customization_utils.dart b/app/lib/ui/components/divider/divider_customization_utils.dart index 617c8ffd..d08df990 100644 --- a/app/lib/ui/components/divider/divider_customization_utils.dart +++ b/app/lib/ui/components/divider/divider_customization_utils.dart @@ -10,8 +10,7 @@ // Software description: Flutter library of reusable graphical components // import 'package:ouds_core/components/divider/ouds_divider.dart'; - -import 'divider_customization.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_enum.dart'; /// Utility class to map divider customization options to corresponding OudsDivider attributes. /// @@ -21,8 +20,8 @@ import 'divider_customization.dart'; /// user-selected options into code that is used for button customization and rendering. class DividerCustomizationUtils { - /// Maps the style enum to `OudsButtonStyle`. - static OudsDividerColor getColor(DividerEnumColor? color) { + /// Maps the color enum to `OudsDividerColor`. + static OudsDividerColor getOudsDividerColor(DividerEnumColor? color) { switch (color) { case DividerEnumColor.onBrandPrimary: return OudsDividerColor.onBrandPrimary; @@ -37,6 +36,8 @@ class DividerCustomizationUtils { case DividerEnumColor.alwaysOnBlack: return OudsDividerColor.alwaysOnBlack; case DividerEnumColor.alwaysWhite: + return OudsDividerColor.alwaysWhite; + case DividerEnumColor.alwaysOnWhite: return OudsDividerColor.alwaysOnWhite; default: return OudsDividerColor.defaultColor; diff --git a/app/lib/ui/components/divider/divider_demo_screen.dart b/app/lib/ui/components/divider/divider_demo_screen.dart index 1a41363d..c915da8f 100644 --- a/app/lib/ui/components/divider/divider_demo_screen.dart +++ b/app/lib/ui/components/divider/divider_demo_screen.dart @@ -1,24 +1,19 @@ - -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:ouds_core/components/divider/ouds_divider.dart'; import 'package:ouds_core/components/ouds_colored_box.dart'; import 'package:ouds_core/components/sheets_bottom/ouds_sheets_bottom.dart'; import 'package:ouds_core/ouds_theme.dart'; import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; +import 'package:ouds_flutter_demo/main_app_bar.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_code_generator.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_customization.dart'; import 'package:ouds_flutter_demo/ui/components/divider/divider_customization_utils.dart'; - +import 'package:ouds_flutter_demo/ui/components/divider/divider_enum.dart'; import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; -import 'package:ouds_flutter_demo/ui/utilities/EnumExt.dart'; -import 'package:provider/provider.dart'; - -import 'package:ouds_flutter_demo/ui/utilities/detail_screen_header.dart'; - -import 'package:ouds_flutter_demo/main_app_bar.dart'; import 'package:ouds_flutter_demo/ui/utilities/code.dart'; -import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_dropdownmenu.dart'; -import 'divider_code_generator.dart'; -import 'divider_customization.dart'; +import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_dropdown_menu.dart'; +import 'package:ouds_flutter_demo/ui/utilities/detail_screen_header.dart'; +import 'package:provider/provider.dart'; class DividerDemoScreen extends StatefulWidget { final bool vertical; @@ -27,19 +22,18 @@ class DividerDemoScreen extends StatefulWidget { @override State createState() => _DividerDemoScreenState(); - } class _DividerDemoScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( + return DividerCustomization( + child: Scaffold( bottomSheet: OudsSheetsBottom( - //onExpansionChanged: _onExpansionChanged, sheetContent: const _CustomizationContent(), title: context.l10n.app_common_customize_label, ), - // key: _scaffoldKey, + // key: _scaffoldKey, appBar: widget.vertical ? MainAppBar(title: context.l10n.app_components_divider_verticalDivider_label) // Display IndeterminateCheckboxDemo if true : MainAppBar(title: context.l10n.app_components_divider_horizontalDivider_label), @@ -49,9 +43,8 @@ class _DividerDemoScreenState extends State { child: _Body(vertical: widget.vertical), ), ), - ); + )); } - } class _CustomizationContent extends StatefulWidget { @@ -59,58 +52,47 @@ class _CustomizationContent extends StatefulWidget { @override State<_CustomizationContent> createState() => _CustomizationContentState(); - - } /// This state class handles the customization options for the checkbox class _CustomizationContentState extends State<_CustomizationContent> { _CustomizationContentState(); - late ValueNotifier dividerColor; - - @override - void initState() { - dividerColor = rememberDividerDemoState(); - } - - ValueNotifier rememberDividerDemoState( - [OudsDividerColor initial = OudsDividerColor.defaultColor]) { + ValueNotifier rememberDividerDemoState([OudsDividerColor initial = OudsDividerColor.defaultColor]) { return ValueNotifier(initial); } @override Widget build(BuildContext context) { - final DividerCustomizationState? customizationState = DividerCustomization.of(context); - var colors = OudsDividerColor.values.toList() - ..sort((color, colorNext) => color.formattedName.compareTo(colorNext.formattedName)); - + //sort alphabetic order + var colors = customizationState!.colorState.list..sort((color, colorNext) => color.formattedName.compareTo(colorNext.formattedName)); return CustomizationDropdownMenu( - label: context.l10n.app_components_common_color_label, - itemLabels: colors.map((color) => color.formattedName).toList(), - selectedItemIndex: colors.indexOf(dividerColor.value), - selectedOption: customizationState?.selectedColor, - onSelectionChange: (value) { + label: DividerEnumColor.enumName(context), + options: colors, + selectedItemIndex: customizationState.selectedIndex, + selectedOption: customizationState.selectedColor, + getText: (option) => option.formattedName, + onSelectionChange: (value, index) { setState(() { - customizationState?.selectedColor = value; + customizationState.selectedColor = value; + customizationState.selectedIndex = index; }); }, - itemLeadingIcons: colors.map((color) { - return () => Container( - width: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, - height: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, - decoration: BoxDecoration( - color: color.getColor(context), - shape: BoxShape.rectangle, - ), - ); - }).toList(), + itemLeadingIcons: customizationState.colorState.list.map((color) { + return () => Container( + width: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, + height: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, + decoration: BoxDecoration( + color: DividerCustomizationUtils.getOudsDividerColor(color).getColor(context), + shape: BoxShape.rectangle, + ), + ); + }).toList(), ); } - } class _Body extends StatefulWidget { @@ -135,7 +117,7 @@ class _BodyState extends State<_Body> { code: DividerCodeGenerator.updateCode(context, widget.vertical), ), ], - ), description: '', + ), ); } } @@ -154,14 +136,12 @@ class _DividerDemo extends StatefulWidget { class _DividerDemoState extends State<_DividerDemo> { ThemeController? themeController; - Color? color ; DividerCustomizationState? customizationState; - - @override Widget build(BuildContext context) { customizationState = DividerCustomization.of(context); + themeController = Provider.of(context, listen: false); // Adding post-frame callback to update theme based on customization state WidgetsBinding.instance.addPostFrameCallback((_) { @@ -169,12 +149,10 @@ class _DividerDemoState extends State<_DividerDemo> { }); return OudsColoredBox( - color: customizationState?.hasOnColoredBox == true ? OudsColoredBoxColor.brandPrimary : null, - child: widget.vertical ? OudsDivider.vertical( - color: DividerCustomizationUtils.getColor(DividerEnumColor.brandPrimary), - ) : OudsDivider.horizontal( - color: DividerCustomizationUtils.getColor(customizationState?.selectedColor) - ), + color: customizationState?.hasOnColoredBox == true ? OudsColoredBoxColor.brandPrimary : OudsColoredBoxColor.statusNeutralMuted, + child: widget.vertical + ? OudsDivider.vertical(color: DividerCustomizationUtils.getOudsDividerColor(customizationState?.selectedColor)) + : OudsDivider.horizontal(color: DividerCustomizationUtils.getOudsDividerColor(customizationState?.selectedColor)), ); } -} \ No newline at end of file +} diff --git a/app/lib/ui/components/divider/divider_enum.dart b/app/lib/ui/components/divider/divider_enum.dart new file mode 100644 index 00000000..237f64f2 --- /dev/null +++ b/app/lib/ui/components/divider/divider_enum.dart @@ -0,0 +1,27 @@ +import 'package:flutter/cupertino.dart'; +import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; + +/// Represents the color of an OUDS divider. +enum DividerEnumColor { + defaultColor, + muted, + emphasized, + brandPrimary, + onBrandPrimary, + alwaysBlack, + alwaysOnBlack, + alwaysWhite, + alwaysOnWhite; + + static String enumName(BuildContext context) { + return context.l10n.app_components_common_color_label; + } +} + +extension FormattedName on Enum { + String get formattedName { + final words = name.split(RegExp(r'(?=\p{Lu})', unicode: true)); + final joined = words.map((w) => w.toLowerCase()).join(' ').trim(); + return joined[0].toUpperCase() + joined.substring(1); + } +} diff --git a/app/lib/ui/utilities/EnumExt.dart b/app/lib/ui/utilities/EnumExt.dart deleted file mode 100644 index 1787e565..00000000 --- a/app/lib/ui/utilities/EnumExt.dart +++ /dev/null @@ -1,8 +0,0 @@ - -extension FormattedName on Enum { - String get formattedName { - final words = name.split(RegExp(r'(?=\p{Lu})', unicode: true)); - final joined = words.map((w) => w.toLowerCase()).join(' ').trim(); - return joined[0].toUpperCase() + joined.substring(1); - } -} diff --git a/app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart b/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart similarity index 51% rename from app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart rename to app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart index 552df5ea..6b072db0 100644 --- a/app/lib/ui/utilities/customizable/customizable_dropdownmenu.dart +++ b/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart @@ -1,53 +1,52 @@ +// +// Software Name: OUDS Flutter +// SPDX-FileCopyrightText: Copyright (c) Orange SA +// SPDX-License-Identifier: MIT +// +// This software is distributed under the MIT license, +// the text of which is available at https://opensource.org/license/MIT/ +// or see the "LICENSE" file for more details. +// +// Software description: Flutter library of reusable graphical components +// import 'package:flutter/material.dart'; import 'package:ouds_core/ouds_theme.dart'; -import 'package:ouds_flutter_demo/ui/components/divider/divider_customization.dart'; import 'package:provider/provider.dart'; import '../../theme/theme_controller.dart'; -class CustomizationDropdownMenu extends StatefulWidget { +class CustomizationDropdownMenu extends StatelessWidget { final String label; - final List itemLabels; - final int selectedItemIndex; - final Function(T) onSelectionChange; + final List options; + int selectedItemIndex; + final String Function(T) getText; + final Function(T, int) onSelectionChange; final List? itemLeadingIcons; - final T? selectedOption; + T? selectedOption; - const CustomizationDropdownMenu({ + var isSelected = false; + + CustomizationDropdownMenu({ super.key, required this.label, - required this.itemLabels, + required this.options, required this.selectedItemIndex, required this.onSelectionChange, required this.selectedOption, this.itemLeadingIcons, + required this.getText, }); - @override - State createState() => _CustomizationDropdownMenuState(); -} - -class _CustomizationDropdownMenuState extends State { - late int selectedIndex; - bool expanded = false; - - @override - void initState() { - super.initState(); - selectedIndex = widget.selectedItemIndex; - } - @override Widget build(BuildContext context) { final themeController = Provider.of(context, listen: false); - return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsetsDirectional.symmetric(horizontal: 16), child: Text( - widget.label, + label, style: TextStyle( fontSize: themeController.currentTheme.fontTokens.sizeBodyLargeMobile, fontWeight: themeController.currentTheme.fontTokens.weightLabelStrong, @@ -57,8 +56,8 @@ class _CustomizationDropdownMenuState extends State { ), Padding( padding: const EdgeInsetsDirectional.symmetric(horizontal: 16, vertical: 8), - child: DropdownMenu( - initialSelection: widget.itemLabels[selectedIndex], + child: DropdownMenu( + initialSelection: options[selectedItemIndex], expandedInsets: EdgeInsets.zero, inputDecorationTheme: const InputDecorationTheme(isDense: true), textStyle: TextStyle( @@ -68,38 +67,25 @@ class _CustomizationDropdownMenuState extends State { ), onSelected: (value) { if (value != null) { - final index = widget.itemLabels.indexOf(value); - setState(() { - selectedIndex = index; - widget.onSelectionChange(value); - }); - + selectedItemIndex = options.indexOf(value); + onSelectionChange(value, selectedItemIndex); } }, - leadingIcon: widget.itemLeadingIcons != null - ? buildDropdownLeadingIcon(widget.itemLeadingIcons, selectedIndex) - : null, - dropdownMenuEntries: - List.generate - (widget.itemLabels.length, (index) { - - final label = widget.itemLabels[index]; - final iconBuilder = widget.itemLeadingIcons != null && index < widget.itemLeadingIcons!.length - ? widget.itemLeadingIcons![index] - : null; - return DropdownMenuEntry( - labelWidget: Text(label, + leadingIcon: itemLeadingIcons != null ? buildDropdownLeadingIcon(context, itemLeadingIcons, selectedItemIndex) : null, + dropdownMenuEntries: List.generate(options.length, (index) { + selectedOption = options[index]; + selectedItemIndex = index; + final iconBuilder = itemLeadingIcons != null && index < itemLeadingIcons!.length ? itemLeadingIcons![index] : null; + return DropdownMenuEntry( + labelWidget: Text(getText(options[index]), style: TextStyle( fontSize: OudsTheme.of(context).fontTokens.sizeBodyLargeMobile, fontWeight: OudsTheme.of(context).fontTokens.weightLabelStrong, letterSpacing: OudsTheme.of(context).fontTokens.letterSpacingBodyLargeMobile, - ) - ), - value: label, - label: label, - leadingIcon: iconBuilder != null - ? iconBuilder() - : null, + )), + value: selectedOption!, + label: getText(options[index]), + leadingIcon: iconBuilder != null ? iconBuilder() : null, ); }), ), @@ -108,12 +94,9 @@ class _CustomizationDropdownMenuState extends State { ); } - Widget? buildDropdownLeadingIcon(List? builders, int index) { + Widget? buildDropdownLeadingIcon(BuildContext context, List? builders, int index) { if (builders != null && index < builders.length) { - return Padding(padding: EdgeInsetsDirectional.all( - OudsTheme.of(context).spaceTokens.paddingInlineShort - ), - child: builders[index]()); + return Padding(padding: EdgeInsetsDirectional.all(OudsTheme.of(context).spaceTokens.paddingInlineShort), child: builders[index]()); } return null; } diff --git a/ouds_core/lib/components/divider/ouds_divider.dart b/ouds_core/lib/components/divider/ouds_divider.dart index 16acb75a..47b7b864 100644 --- a/ouds_core/lib/components/divider/ouds_divider.dart +++ b/ouds_core/lib/components/divider/ouds_divider.dart @@ -11,7 +11,6 @@ // import 'package:flutter/material.dart'; - import 'package:ouds_core/ouds_theme.dart'; /// TODO Add DSM link when available @@ -35,42 +34,42 @@ import 'package:ouds_core/ouds_theme.dart'; /// /// Represents the available colors for [OudsDivider]. - enum OudsDividerColor { - defaultColor, - muted, - emphasized, - brandPrimary, - onBrandPrimary, - alwaysBlack, - alwaysOnBlack, - alwaysWhite, - alwaysOnWhite; - - Color getColor(BuildContext context) { - final theme = OudsTheme.of(context); - - switch (this) { - case OudsDividerColor.muted: - return theme.colorsScheme.borderMuted; - case OudsDividerColor.emphasized: - return theme.colorsScheme.borderEmphasized; - case OudsDividerColor.brandPrimary: - return theme.colorsScheme.borderBrandPrimary; - case OudsDividerColor.onBrandPrimary: - return theme.colorsScheme.borderOnBrandPrimary; - case OudsDividerColor.alwaysBlack: - return theme.colorsScheme.alwaysBlack; - case OudsDividerColor.alwaysOnBlack: - return theme.colorsScheme.alwaysOnBlack; - case OudsDividerColor.alwaysWhite: - return theme.colorsScheme.alwaysWhite; - case OudsDividerColor.alwaysOnWhite: - return theme.colorsScheme.alwaysOnWhite; - default: - return theme.colorsScheme.borderDefault; +enum OudsDividerColor { + defaultColor, + muted, + emphasized, + brandPrimary, + onBrandPrimary, + alwaysBlack, + alwaysOnBlack, + alwaysWhite, + alwaysOnWhite; + + Color getColor(BuildContext context) { + final theme = OudsTheme.of(context); + + switch (this) { + case OudsDividerColor.muted: + return theme.colorsScheme.borderMuted; + case OudsDividerColor.emphasized: + return theme.colorsScheme.borderEmphasized; + case OudsDividerColor.brandPrimary: + return theme.colorsScheme.borderBrandPrimary; + case OudsDividerColor.onBrandPrimary: + return theme.colorsScheme.borderOnBrandPrimary; + case OudsDividerColor.alwaysBlack: + return theme.colorsScheme.alwaysBlack; + case OudsDividerColor.alwaysOnBlack: + return theme.colorsScheme.alwaysOnBlack; + case OudsDividerColor.alwaysWhite: + return theme.colorsScheme.alwaysWhite; + case OudsDividerColor.alwaysOnWhite: + return theme.colorsScheme.alwaysOnWhite; + default: + return theme.colorsScheme.borderDefault; } } - } +} class OudsDivider extends StatelessWidget { final Axis orientation; @@ -85,29 +84,26 @@ class OudsDivider extends StatelessWidget { this.length = double.infinity, this.thickness = 1, this.margin, - }) : orientation = Axis.horizontal; + }) : orientation = Axis.horizontal; /// Creates a vertical divider. const OudsDivider.vertical({ this.color = OudsDividerColor.defaultColor, - this.length = 100, + this.length = 0, this.thickness = 1, this.margin, - }) : orientation = Axis.vertical; - + }) : orientation = Axis.vertical; @override Widget build(BuildContext context) { - var colors = OudsDividerColor.values; - final divider = Container( - color: colors.first.getColor(context), + color: color.getColor(context), width: orientation == Axis.horizontal ? length : thickness, - height: orientation == Axis.horizontal ? thickness : length, + height: orientation == Axis.horizontal ? thickness : 50, margin: margin, ); - return divider; + return Padding(padding: EdgeInsetsDirectional.all(OudsTheme.of(context).spaceTokens.fixedMedium), child: divider); } } From acacfee3e0d293d63f1a971dc348e6d5ec496f84 Mon Sep 17 00:00:00 2001 From: "n.hammami" Date: Wed, 14 May 2025 16:21:00 +0100 Subject: [PATCH 09/14] update: update CHANGELOG.md in demo app and lib --- app/CHANGELOG.md | 1 + app/lib/ui/utilities/app_assets.dart | 12 ++++++++---- ouds_core/CHANGELOG.md | 3 +++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/CHANGELOG.md b/app/CHANGELOG.md index 71970e5e..14c7e209 100644 --- a/app/CHANGELOG.md +++ b/app/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/Orange-OpenSource/ouds-flutter/compare/0.2.0...develop) ### Added +- [DemoApp][Library] Create component - `Divider` ([#57](https://github.com/Orange-OpenSource/ouds-flutter/issues/57)) - [Library] `Switch` component (tokens library v0.11.0) ([##182](https://github.com/Orange-OpenSource/ouds-flutter/issues/#182) - [Tool] Added Dependabot configuration for automatic dependency updates ([#154](https://github.com/Orange-OpenSource/ouds-flutter/issues/154)) - [Library] `Divider` component (tokens library v0.11.0) ([#151](https://github.com/Orange-OpenSource/ouds-flutter/issues/151)) diff --git a/app/lib/ui/utilities/app_assets.dart b/app/lib/ui/utilities/app_assets.dart index f5dc983c..7fba5f87 100644 --- a/app/lib/ui/utilities/app_assets.dart +++ b/app/lib/ui/utilities/app_assets.dart @@ -20,11 +20,15 @@ class AppAssets { } class _Images { - _Images(); + _Images(); + /// Components final String ilComponentsButton = 'assets/il_components_button.svg'; final String ilComponentsButtonDark = 'assets/il_components_button_dark.svg'; + final String ilComponentsDivider = 'assets/il_components_divider.svg'; + final String ilComponentsDividerDark = 'assets/il_components_divider_dark.svg'; + ///Tokens final String ilTokensColor = 'assets/il_tokens_color.svg'; final String ilTokensColorDark = 'assets/il_tokens_color_dark.svg'; @@ -35,9 +39,8 @@ class _Images { final String ilTokensOpacity = 'assets/il_tokens_opacity.svg'; final String ilTokensOpacityDark = 'assets/il_tokens_opacity_dark.svg'; - final String ilUnion = 'assets/il_union.svg'; - final String ilUnionDark = 'assets/il_union_dark.svg'; - + final String ilUnion = 'assets/il_union.svg'; + final String ilUnionDark = 'assets/il_union_dark.svg'; } class _Icons { @@ -49,6 +52,7 @@ class _Icons { final String icThemeSystem = 'assets/ic_theme_system.svg'; final String icToken = 'assets/ic_token.svg'; } + class _Fonts { const _Fonts(); } diff --git a/ouds_core/CHANGELOG.md b/ouds_core/CHANGELOG.md index be2d9876..c4b06d1e 100644 --- a/ouds_core/CHANGELOG.md +++ b/ouds_core/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/Orange-OpenSource/ouds-flutter/compare/0.2.0...develop) +### Added +- [Library] Create component - Divider ([#57](https://github.com/Orange-OpenSource/ouds-flutter/issues/57)) + ### Fixed - [Library] Clean project ouds_core ([#185](https://github.com/Orange-OpenSource/ouds-flutter/issues/185)) From 00787017d331190cb33c0f8e851ca770e4d3da52 Mon Sep 17 00:00:00 2001 From: "n.hammami" Date: Thu, 15 May 2025 12:20:28 +0100 Subject: [PATCH 10/14] review: update files with recommended changes --- .../components/divider/divider_code_generator.dart | 2 +- .../ui/components/divider/divider_demo_screen.dart | 10 +++------- .../customizable/customizable_dropdown_menu.dart | 13 ++++++------- ouds_core/lib/components/divider/ouds_divider.dart | 14 +++++++++----- .../tokens/components/ouds_divider_tokens.dart | 14 +------------- 5 files changed, 20 insertions(+), 33 deletions(-) diff --git a/app/lib/ui/components/divider/divider_code_generator.dart b/app/lib/ui/components/divider/divider_code_generator.dart index 7374bb76..51dc12a8 100644 --- a/app/lib/ui/components/divider/divider_code_generator.dart +++ b/app/lib/ui/components/divider/divider_code_generator.dart @@ -30,7 +30,7 @@ class DividerCodeGenerator { // Method to get the function name according to the orientation of divider static String functionName(bool vertical) { // Return the function name based on the orientation of divider - return "${vertical == true ? 'OudsVerticalDivider' : 'OudsHorizontalDivider'}"; + return vertical == true ? 'OudsVerticalDivider' : 'OudsHorizontalDivider'; } // Method to generate the selected color diff --git a/app/lib/ui/components/divider/divider_demo_screen.dart b/app/lib/ui/components/divider/divider_demo_screen.dart index c915da8f..845d29be 100644 --- a/app/lib/ui/components/divider/divider_demo_screen.dart +++ b/app/lib/ui/components/divider/divider_demo_screen.dart @@ -18,7 +18,7 @@ import 'package:provider/provider.dart'; class DividerDemoScreen extends StatefulWidget { final bool vertical; - const DividerDemoScreen({required this.vertical}); + const DividerDemoScreen({super.key, required this.vertical}); @override State createState() => _DividerDemoScreenState(); @@ -58,10 +58,6 @@ class _CustomizationContent extends StatefulWidget { class _CustomizationContentState extends State<_CustomizationContent> { _CustomizationContentState(); - ValueNotifier rememberDividerDemoState([OudsDividerColor initial = OudsDividerColor.defaultColor]) { - return ValueNotifier(initial); - } - @override Widget build(BuildContext context) { final DividerCustomizationState? customizationState = DividerCustomization.of(context); @@ -83,8 +79,8 @@ class _CustomizationContentState extends State<_CustomizationContent> { }, itemLeadingIcons: customizationState.colorState.list.map((color) { return () => Container( - width: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, - height: OudsTheme.of(context).componentsTokens.divider.mediumSizeIconWithText, + width: OudsTheme.of(context).spaceTokens.paddingBlockSpacious, + height: OudsTheme.of(context).spaceTokens.paddingBlockSpacious, decoration: BoxDecoration( color: DividerCustomizationUtils.getOudsDividerColor(color).getColor(context), shape: BoxShape.rectangle, diff --git a/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart b/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart index 6b072db0..e5483872 100644 --- a/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart +++ b/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart @@ -11,10 +11,9 @@ // import 'package:flutter/material.dart'; import 'package:ouds_core/ouds_theme.dart'; +import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; import 'package:provider/provider.dart'; -import '../../theme/theme_controller.dart'; - class CustomizationDropdownMenu extends StatelessWidget { final String label; final List options; @@ -40,11 +39,12 @@ class CustomizationDropdownMenu extends StatelessWidget { @override Widget build(BuildContext context) { final themeController = Provider.of(context, listen: false); + final currentTheme = themeController.currentTheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsetsDirectional.symmetric(horizontal: 16), + padding: EdgeInsetsDirectional.symmetric(horizontal: currentTheme.spaceTokens.fixedMedium), child: Text( label, style: TextStyle( @@ -55,7 +55,7 @@ class CustomizationDropdownMenu extends StatelessWidget { ), ), Padding( - padding: const EdgeInsetsDirectional.symmetric(horizontal: 16, vertical: 8), + padding: EdgeInsetsDirectional.symmetric(horizontal: currentTheme.spaceTokens.fixedMedium, vertical: currentTheme.spaceTokens.fixedShorter), child: DropdownMenu( initialSelection: options[selectedItemIndex], expandedInsets: EdgeInsets.zero, @@ -75,7 +75,6 @@ class CustomizationDropdownMenu extends StatelessWidget { dropdownMenuEntries: List.generate(options.length, (index) { selectedOption = options[index]; selectedItemIndex = index; - final iconBuilder = itemLeadingIcons != null && index < itemLeadingIcons!.length ? itemLeadingIcons![index] : null; return DropdownMenuEntry( labelWidget: Text(getText(options[index]), style: TextStyle( @@ -83,9 +82,9 @@ class CustomizationDropdownMenu extends StatelessWidget { fontWeight: OudsTheme.of(context).fontTokens.weightLabelStrong, letterSpacing: OudsTheme.of(context).fontTokens.letterSpacingBodyLargeMobile, )), - value: selectedOption!, + value: selectedOption as T, label: getText(options[index]), - leadingIcon: iconBuilder != null ? iconBuilder() : null, + leadingIcon: itemLeadingIcons != null ? buildDropdownLeadingIcon(context, itemLeadingIcons, selectedItemIndex) : null, ); }), ), diff --git a/ouds_core/lib/components/divider/ouds_divider.dart b/ouds_core/lib/components/divider/ouds_divider.dart index 47b7b864..100801f5 100644 --- a/ouds_core/lib/components/divider/ouds_divider.dart +++ b/ouds_core/lib/components/divider/ouds_divider.dart @@ -75,31 +75,35 @@ class OudsDivider extends StatelessWidget { final Axis orientation; final OudsDividerColor color; final double length; - final double thickness; + final double? thickness; final EdgeInsetsGeometry? margin; /// Creates a horizontal divider. const OudsDivider.horizontal({ + super.key, this.color = OudsDividerColor.defaultColor, this.length = double.infinity, - this.thickness = 1, + this.thickness, this.margin, }) : orientation = Axis.horizontal; /// Creates a vertical divider. const OudsDivider.vertical({ + super.key, this.color = OudsDividerColor.defaultColor, this.length = 0, - this.thickness = 1, + this.thickness, this.margin, }) : orientation = Axis.vertical; @override Widget build(BuildContext context) { + final actualThickness = thickness ?? OudsTheme.of(context).componentsTokens.divider.borderWidth; + final divider = Container( color: color.getColor(context), - width: orientation == Axis.horizontal ? length : thickness, - height: orientation == Axis.horizontal ? thickness : 50, + width: orientation == Axis.horizontal ? length : actualThickness, + height: orientation == Axis.horizontal ? actualThickness : 50, margin: margin, ); diff --git a/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart b/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart index deaa5487..0af4d9e5 100644 --- a/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart +++ b/ouds_theme_contract/lib/theme/tokens/components/ouds_divider_tokens.dart @@ -13,22 +13,10 @@ // Tokens version 0.11.0 // Generated by Tokenator -import 'package:flutter/material.dart'; import 'package:ouds_theme_contract/ouds_tokens_provider.dart'; class OudsDividerTokens { final double borderWidth; - final double mediumSizeIconWithText; - final double smallSizeIconWithText; - - OudsDividerTokens({ - required OudsProvidersTokens providersTokens, - double? borderWidth, - double? mediumSizeIconWithText, - double? smallSizeIconWithText - }) : - borderWidth = borderWidth ?? providersTokens.borderTokens.widthThin, - mediumSizeIconWithText = mediumSizeIconWithText ?? providersTokens.sizeTokens.iconWithLabelMediumSizeLg, - smallSizeIconWithText = smallSizeIconWithText ?? providersTokens.sizeTokens.iconWithLabelSmallSizeLg; + OudsDividerTokens({required OudsProvidersTokens providersTokens, double? borderWidth}) : borderWidth = borderWidth ?? providersTokens.borderTokens.widthThin; } From 2c27babddd407836d30a8997c268eab116f4003c Mon Sep 17 00:00:00 2001 From: Tayeb Sedraia Date: Thu, 15 May 2025 13:45:11 +0200 Subject: [PATCH 11/14] review: delete warning --- app/lib/ui/components/components_screen.dart | 2 -- .../customizable_dropdown_menu.dart | 20 +++++++++---------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/lib/ui/components/components_screen.dart b/app/lib/ui/components/components_screen.dart index a796e3b4..b923a2ae 100644 --- a/app/lib/ui/components/components_screen.dart +++ b/app/lib/ui/components/components_screen.dart @@ -19,8 +19,6 @@ import 'package:ouds_flutter_demo/ui/utilities/cards/ouds_cards_common.dart'; import 'package:ouds_flutter_demo/ui/utilities/cards/ouds_vertical_image_first_card.dart'; import 'package:provider/provider.dart'; -import 'package:ouds_flutter_demo/ui/components/component_variants_screen.dart'; - class ComponentsScreen extends StatelessWidget { final List oudsComponents; diff --git a/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart b/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart index e5483872..95a1750c 100644 --- a/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart +++ b/app/lib/ui/utilities/customizable/customizable_dropdown_menu.dart @@ -17,15 +17,14 @@ import 'package:provider/provider.dart'; class CustomizationDropdownMenu extends StatelessWidget { final String label; final List options; - int selectedItemIndex; + final int selectedItemIndex; final String Function(T) getText; final Function(T, int) onSelectionChange; final List? itemLeadingIcons; - T? selectedOption; + final T? selectedOption; + final bool isSelected; - var isSelected = false; - - CustomizationDropdownMenu({ + const CustomizationDropdownMenu({ super.key, required this.label, required this.options, @@ -34,6 +33,7 @@ class CustomizationDropdownMenu extends StatelessWidget { required this.selectedOption, this.itemLeadingIcons, required this.getText, + this.isSelected = false, }); @override @@ -67,14 +67,12 @@ class CustomizationDropdownMenu extends StatelessWidget { ), onSelected: (value) { if (value != null) { - selectedItemIndex = options.indexOf(value); - onSelectionChange(value, selectedItemIndex); + final newIndex = options.indexOf(value); + onSelectionChange(value, newIndex); } }, leadingIcon: itemLeadingIcons != null ? buildDropdownLeadingIcon(context, itemLeadingIcons, selectedItemIndex) : null, dropdownMenuEntries: List.generate(options.length, (index) { - selectedOption = options[index]; - selectedItemIndex = index; return DropdownMenuEntry( labelWidget: Text(getText(options[index]), style: TextStyle( @@ -82,9 +80,9 @@ class CustomizationDropdownMenu extends StatelessWidget { fontWeight: OudsTheme.of(context).fontTokens.weightLabelStrong, letterSpacing: OudsTheme.of(context).fontTokens.letterSpacingBodyLargeMobile, )), - value: selectedOption as T, + value: options[index], label: getText(options[index]), - leadingIcon: itemLeadingIcons != null ? buildDropdownLeadingIcon(context, itemLeadingIcons, selectedItemIndex) : null, + leadingIcon: itemLeadingIcons != null ? buildDropdownLeadingIcon(context, itemLeadingIcons, index) : null, ); }), ), From 85dd6b576494ea411bbb1d831def7dd9dc06cb7e Mon Sep 17 00:00:00 2001 From: Tayeb Sedraia Date: Thu, 15 May 2025 13:53:05 +0200 Subject: [PATCH 12/14] fix: build test pipeline fail --- app/lib/ui/components/divider/divider_code_generator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/ui/components/divider/divider_code_generator.dart b/app/lib/ui/components/divider/divider_code_generator.dart index 51dc12a8..363e7f94 100644 --- a/app/lib/ui/components/divider/divider_code_generator.dart +++ b/app/lib/ui/components/divider/divider_code_generator.dart @@ -11,8 +11,8 @@ // import 'package:flutter/material.dart'; -import 'package:ouds_flutter_demo/ui/components/divider/Divider_customization_utils.dart'; import 'package:ouds_flutter_demo/ui/components/divider/divider_customization.dart'; +import 'package:ouds_flutter_demo/ui/components/divider/divider_customization_utils.dart'; /// /// The CheckboxCodeGenerator class is responsible for dynamically generating Flutter From 9640d007cd4314f4374f262781ec79235c60a3b1 Mon Sep 17 00:00:00 2001 From: Tayeb Sedraia Date: Thu, 15 May 2025 14:50:10 +0200 Subject: [PATCH 13/14] fix : import from rebase on develop --- app/lib/ui/components/divider/divider_demo_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/ui/components/divider/divider_demo_screen.dart b/app/lib/ui/components/divider/divider_demo_screen.dart index 845d29be..9fb423c9 100644 --- a/app/lib/ui/components/divider/divider_demo_screen.dart +++ b/app/lib/ui/components/divider/divider_demo_screen.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:ouds_core/components/divider/ouds_divider.dart'; import 'package:ouds_core/components/ouds_colored_box.dart'; -import 'package:ouds_core/components/sheets_bottom/ouds_sheets_bottom.dart'; import 'package:ouds_core/ouds_theme.dart'; import 'package:ouds_flutter_demo/l10n/app_localizations.dart'; import 'package:ouds_flutter_demo/main_app_bar.dart'; @@ -13,6 +12,7 @@ import 'package:ouds_flutter_demo/ui/theme/theme_controller.dart'; import 'package:ouds_flutter_demo/ui/utilities/code.dart'; import 'package:ouds_flutter_demo/ui/utilities/customizable/customizable_dropdown_menu.dart'; import 'package:ouds_flutter_demo/ui/utilities/detail_screen_header.dart'; +import 'package:ouds_flutter_demo/ui/utilities/sheets_bottom/ouds_sheets_bottom.dart'; import 'package:provider/provider.dart'; class DividerDemoScreen extends StatefulWidget { From aaffbde64af9f6bed9e018c576c1770c772cec42 Mon Sep 17 00:00:00 2001 From: "n.hammami" Date: Wed, 21 May 2025 14:16:03 +0100 Subject: [PATCH 14/14] review: change the formatted name of enum for DefaultColor to Default --- app/lib/ui/components/divider/divider_enum.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/app/lib/ui/components/divider/divider_enum.dart b/app/lib/ui/components/divider/divider_enum.dart index 237f64f2..30ee5f9f 100644 --- a/app/lib/ui/components/divider/divider_enum.dart +++ b/app/lib/ui/components/divider/divider_enum.dart @@ -20,6 +20,7 @@ enum DividerEnumColor { extension FormattedName on Enum { String get formattedName { + if (name == 'defaultColor') return 'Default'; final words = name.split(RegExp(r'(?=\p{Lu})', unicode: true)); final joined = words.map((w) => w.toLowerCase()).join(' ').trim(); return joined[0].toUpperCase() + joined.substring(1);