From 121145a9d9d1d00d86a979c363123beb7bafd37d Mon Sep 17 00:00:00 2001 From: behnam-deriv <133759298+behnam-deriv@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:37:17 +0800 Subject: [PATCH 1/7] feat: make chart indicators panels resizable --- lib/deriv_chart.dart | 1 + lib/src/deriv_chart/chart/bottom_chart.dart | 11 +- .../chart/bottom_chart_mobile.dart | 8 +- lib/src/deriv_chart/chart/chart.dart | 161 +++++++++++++- .../deriv_chart/chart/chart_state_mobile.dart | 208 +++++++++++------- .../deriv_chart/chart/chart_state_web.dart | 166 +++++++++----- .../panel_size/panel_size_repository.dart | 65 ++++++ .../chart/resizable_chart_divider.dart | 132 +++++++++++ lib/src/deriv_chart/deriv_chart.dart | 14 ++ lib/src/theme/chart_default_dark_theme.dart | 3 + lib/src/theme/chart_default_light_theme.dart | 3 + lib/src/theme/chart_theme.dart | 4 + lib/src/theme/colors.dart | 6 + lib/src/theme/dimens.dart | 14 ++ .../chart/panel_fractions_test.dart | 112 ++++++++++ .../chart/panel_size_repository_test.dart | 75 +++++++ .../chart/resizable_chart_divider_test.dart | 64 ++++++ 17 files changed, 902 insertions(+), 145 deletions(-) create mode 100644 lib/src/deriv_chart/chart/panel_size/panel_size_repository.dart create mode 100644 lib/src/deriv_chart/chart/resizable_chart_divider.dart create mode 100644 test/deriv_chart/chart/panel_fractions_test.dart create mode 100644 test/deriv_chart/chart/panel_size_repository_test.dart create mode 100644 test/deriv_chart/chart/resizable_chart_divider_test.dart diff --git a/lib/deriv_chart.dart b/lib/deriv_chart.dart index ae59cabe2..bbe5c5310 100644 --- a/lib/deriv_chart.dart +++ b/lib/deriv_chart.dart @@ -43,6 +43,7 @@ export 'src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart' export 'src/add_ons/repository.dart'; export 'src/deriv_chart/interactive_layer/interactive_layer_export.dart'; export 'src/deriv_chart/chart/chart.dart'; +export 'src/deriv_chart/chart/panel_size/panel_size_repository.dart'; export 'src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_active_contract.dart'; export 'src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_closed_indicator.dart'; export 'src/deriv_chart/chart/data_visualization/annotations/barriers/accumulators_barriers/accumulators_entry_spot_barrier.dart'; diff --git a/lib/src/deriv_chart/chart/bottom_chart.dart b/lib/src/deriv_chart/chart/bottom_chart.dart index b82d5533f..db4ee53ce 100644 --- a/lib/src/deriv_chart/chart/bottom_chart.dart +++ b/lib/src/deriv_chart/chart/bottom_chart.dart @@ -196,16 +196,7 @@ class _BottomChartState extends BasicChartState { child: ClipRect( child: Stack( children: [ - Column( - children: [ - Divider( - height: 0.5, - thickness: 1, - color: theme.base01Color, - ), - Expanded(child: super.build(context)), - ], - ), + super.build(context), // if (kIsWeb) _buildCrosshairAreaWeb(), // TODO(Jim): Address the crosshair area for web indicators. _buildBottomChartOptions(context) ], diff --git a/lib/src/deriv_chart/chart/bottom_chart_mobile.dart b/lib/src/deriv_chart/chart/bottom_chart_mobile.dart index bfb175142..0d303411a 100644 --- a/lib/src/deriv_chart/chart/bottom_chart_mobile.dart +++ b/lib/src/deriv_chart/chart/bottom_chart_mobile.dart @@ -92,13 +92,7 @@ class _BottomChartMobileState extends BasicChartState { : Stack( children: [ if (widget.showFrame) _buildChartFrame(context), - if (!widget.isHidden) - Column( - children: [ - Expanded(child: super.build(context)), - _buildDivider(), - ], - ), + if (!widget.isHidden) super.build(context), Positioned( top: 4, left: widget.bottomChartTitleMargin?.left ?? 10, diff --git a/lib/src/deriv_chart/chart/chart.dart b/lib/src/deriv_chart/chart/chart.dart index f608b7bda..423a8175a 100644 --- a/lib/src/deriv_chart/chart/chart.dart +++ b/lib/src/deriv_chart/chart/chart.dart @@ -1,6 +1,7 @@ -import 'package:collection/collection.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/chart_scale_model.dart'; import 'package:deriv_chart/src/deriv_chart/chart/mobile_chart_frame_dividers.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/panel_size/panel_size_repository.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/resizable_chart_divider.dart'; import 'package:deriv_chart/src/deriv_chart/chart/x_axis/x_axis_model.dart'; import 'package:deriv_chart/src/deriv_chart/interactive_layer/crosshair/crosshair_variant.dart'; import 'package:deriv_chart/src/theme/dimens.dart'; @@ -39,6 +40,96 @@ part 'chart_state_mobile.dart'; const Duration _defaultDuration = Duration(milliseconds: 300); +/// Ensures [fractions] has exactly one entry per key in [keys], seeding new +/// keys from [saved] (if previously persisted) or from [defaultFraction], +/// dropping any key no longer present in [keys], and renormalizing so the +/// remaining fractions always sum to `1.0`. +/// +/// [fractions] is expected to represent one independent group of sibling +/// panels whose heights add up to the full space they share - e.g. the main +/// chart plus every bottom panel, or (on mobile) the individual indicator +/// panels sharing the bottom section. +void syncPanelFractions( + Map fractions, + List keys, + Map saved, + double Function(String key) defaultFraction, +) { + final Set keySet = keys.toSet(); + fractions.removeWhere((String key, _) => !keySet.contains(key)); + + for (final String key in keys) { + fractions[key] ??= saved[key] ?? defaultFraction(key); + } + + final double sum = fractions.values.fold(0, (double a, double b) => a + b); + if (sum > 0 && sum != 1.0) { + fractions.updateAll((_, double value) => value / sum); + } +} + +/// Cascading resize: dragging the divider between `orderedKeys[dividerIndex]` +/// and `orderedKeys[dividerIndex + 1]` grows one side by [deltaFraction] and +/// shrinks the other. A positive [deltaFraction] grows +/// `orderedKeys[dividerIndex]`; a negative one grows +/// `orderedKeys[dividerIndex + 1]`. +/// +/// The space to shrink is taken from the nearest neighbor on the shrinking +/// side first; if that neighbor is already at the minimum height (a fixed +/// share of the total, [Dimens.minChartPanelHeightFraction], rather than a +/// fixed pixel amount - so it scales down on small screens instead of +/// eating an unreasonably large share of them), the remainder cascades +/// further down the chain (e.g. dragging the divider between a second and +/// third panel can shrink the first panel too, once the second has nothing +/// left to give) so resizing never gets stuck behind an in-between panel +/// that's already at its minimum. Returns whether [fractions] was changed. +bool resizeCascadingFractions( + Map fractions, + List orderedKeys, + int dividerIndex, + double deltaFraction, +) { + if (deltaFraction == 0) { + return false; + } + + const double minFraction = Dimens.minChartPanelHeightFraction; + + final String recipientKey; + final List donorChain; + if (deltaFraction > 0) { + recipientKey = orderedKeys[dividerIndex]; + donorChain = orderedKeys.sublist(dividerIndex + 1); + } else { + recipientKey = orderedKeys[dividerIndex + 1]; + donorChain = orderedKeys.sublist(0, dividerIndex + 1).reversed.toList(); + } + + double remaining = deltaFraction.abs(); + double actualDelta = 0; + for (final String donor in donorChain) { + if (remaining <= 0) { + break; + } + final double current = fractions[donor] ?? 0; + final double avail = current - minFraction; + if (avail <= 0) { + continue; + } + final double take = avail < remaining ? avail : remaining; + fractions[donor] = current - take; + remaining -= take; + actualDelta += take; + } + + if (actualDelta <= 0) { + return false; + } + + fractions[recipientKey] = (fractions[recipientKey] ?? 0) + actualDelta; + return true; +} + /// Interactive chart widget. class Chart extends StatefulWidget { /// Creates chart that expands to available space. @@ -79,6 +170,7 @@ class Chart extends StatefulWidget { this.showScrollToLastTickButton, this.loadingAnimationColor, this.useDrawingToolsV2 = false, + this.panelSizeRepo, Key? key, }) : super(key: key); @@ -202,6 +294,10 @@ class Chart extends StatefulWidget { /// The interactive layer behaviour. final InteractiveLayerBehaviour? interactiveLayerBehaviour; + /// Persists the relative sizes of the main chart and bottom indicator + /// panels as the user drags [ResizableChartDivider]s between them. + final PanelSizeRepository? panelSizeRepo; + @override State createState() => // TODO(Ramin): Make this customizable from outside. @@ -216,6 +312,11 @@ abstract class _ChartState extends State with WidgetsBindingObserver { late List? bottomSeries; int? expandedIndex; + /// Current fraction of the available height occupied by each chart panel, + /// keyed by [PanelSizeRepository.mainPanelKey] for the main chart and by + /// [IndicatorConfig.configId] for each bottom indicator panel. + final Map _panelFractions = {}; + @override void initState() { super.initState(); @@ -255,6 +356,64 @@ abstract class _ChartState extends State with WidgetsBindingObserver { : ChartDefaultLightTheme()); } + /// Ensures [_panelFractions] has exactly one entry per key in [keys], + /// seeding new keys from [widget.panelSizeRepo] (if previously saved) or + /// from [defaultFraction], dropping stale keys, and renormalizing so the + /// fractions always sum to `1.0`. + void _syncPanelFractions( + List keys, + double Function(String key) defaultFraction, + ) => + syncPanelFractions( + _panelFractions, + keys, + widget.panelSizeRepo?.fractions ?? const {}, + defaultFraction, + ); + + /// Cascading resize of the divider at [dividerIndex] within + /// [_panelFractions]. See [resizeCascadingFractions]. + void _resizeCascadingPanels( + List orderedKeys, + int dividerIndex, + double deltaFraction, + ) { + if (resizeCascadingFractions( + _panelFractions, + orderedKeys, + dividerIndex, + deltaFraction, + )) { + setState(() {}); + } + } + + /// Persists [_panelFractions], merged with any [extraFractions] (used by + /// mobile to also persist the relative sizes of individual bottom + /// indicator panels, which are tracked in a separate map). + void _persistPanelFractions([ + Map extraFractions = const {}, + ]) { + widget.panelSizeRepo?.save({ + ..._panelFractions, + ...extraFractions, + }); + } + + /// Key for [config]'s panel within [_panelFractions]/[PanelSizeRepository], + /// falling back to a stable per-index name if it doesn't have a + /// [IndicatorConfig.configId] yet (defensive - repo-managed indicators + /// always get one assigned). + String _panelKeyFor(IndicatorConfig config, int index) => + config.configId ?? 'bottom_$index'; + + /// Height available for panel fractions once [dividerCount] + /// [ResizableChartDivider]s - which take up real space in the same + /// Column as the panels - have been subtracted from [totalHeight]. + double _usableHeightFor(double totalHeight, int dividerCount) => + (totalHeight - dividerCount * Dimens.chartPanelDividerHitHeight) + .clamp(0.0, double.infinity); + void _onCrosshairHover( Offset globalPosition, Offset localPosition, diff --git a/lib/src/deriv_chart/chart/chart_state_mobile.dart b/lib/src/deriv_chart/chart/chart_state_mobile.dart index f2326e93f..98eb6338a 100644 --- a/lib/src/deriv_chart/chart/chart_state_mobile.dart +++ b/lib/src/deriv_chart/chart/chart_state_mobile.dart @@ -1,27 +1,6 @@ part of 'chart.dart'; class _ChartStateMobile extends _ChartState { - double _bottomSectionHeight = 0; - - @override - void initState() { - super.initState(); - - _bottomSectionHeight = - _getBottomIndicatorsSectionHeightFraction(widget.bottomConfigs.length); - } - - @override - void didUpdateWidget(covariant Chart oldWidget) { - super.didUpdateWidget(oldWidget); - - if (oldWidget.bottomConfigs.length != widget.bottomConfigs.length) { - _bottomSectionHeight = _getBottomIndicatorsSectionHeightFraction( - widget.bottomConfigs.length, - ); - } - } - @override Widget buildChartsLayout( BuildContext context, @@ -34,54 +13,124 @@ class _ChartStateMobile extends _ChartState { final Duration quoteBoundsAnimationDuration = widget.quoteBoundsAnimationDuration ?? _defaultDuration; - List getBottomIndicatorsList(BuildContext context) => - widget.indicatorsRepo!.items - .mapIndexed((int index, IndicatorConfig config) { - if (config.isOverlay) { - return const SizedBox.shrink(); - } - - final Series series = config.getSeries( - IndicatorInput( - widget.mainSeries.input, - widget.granularity, + final Repository? repository = widget.indicatorsRepo; + + // Bottom (non-overlay) indicators, in repo order, keeping their true + // index within `repository.items` (needed for hidden-status lookups). + final List bottomRepoIndices = [ + if (repository != null) + for (int i = 0; i < repository.items.length; i++) + if (!repository.items[i].isOverlay) i + ]; + + final List visibleBottomRepoIndices = bottomRepoIndices + .where((int i) => !repository!.getHiddenStatus(i)) + .toList(); + + final List visibleIndicatorKeys = visibleBottomRepoIndices + .map((int i) => _panelKeyFor(repository!.items[i], i)) + .toList(); + + // A single flat, ordered chain covering the main chart and every + // *visible* bottom indicator panel. Keeping this as one chain (rather + // than resizing the bottom section as a whole and then splitting it + // among indicators independently) is what lets a resize cascade past + // an indicator already at its minimum height into the next one that + // still has room, no matter which divider is being dragged. + final List orderedKeys = [ + PanelSizeRepository.mainPanelKey, + ...visibleIndicatorKeys, + ]; + + final double bottomSectionDefaultFraction = + _getBottomIndicatorsSectionHeightFraction(bottomRepoIndices.length); + + _syncPanelFractions( + orderedKeys, + (String key) => key == PanelSizeRepository.mainPanelKey + ? 1 - bottomSectionDefaultFraction + : bottomSectionDefaultFraction / + (visibleIndicatorKeys.isEmpty ? 1 : visibleIndicatorKeys.length), + ); + + List getBottomIndicatorsList( + BuildContext context, + double usableHeight, + ) { + final List children = []; + int visiblePosition = 0; + + for (final int repoIndex in bottomRepoIndices) { + final IndicatorConfig config = repository!.items[repoIndex]; + final bool isHidden = repository.getHiddenStatus(repoIndex); + + final Series series = config.getSeries( + IndicatorInput( + widget.mainSeries.input, + widget.granularity, + ), + ); + + // TODO(Ramin): Use the key (type + number) once it's implemented. + final int indexInBottomConfigs = + referenceIndexOf(widget.bottomConfigs, config); + + final Widget bottomChart = BottomChartMobile( + series: series, + isHidden: isHidden, + granularity: widget.granularity, + pipSize: config.pipSize, + title: + '${config.shortTitle} ${config.number > 0 ? config.number : ''}' + ' (${config.configSummary})', + currentTickAnimationDuration: currentTickAnimationDuration, + quoteBoundsAnimationDuration: quoteBoundsAnimationDuration, + bottomChartTitleMargin: const EdgeInsets.only(left: Dimens.margin04), + onHideUnhideToggle: () => + _onIndicatorHideToggleTapped(repository, repoIndex), + onSwap: (int offset) => _onSwap( + config, widget.bottomConfigs[indexInBottomConfigs + offset]), + showMoveUpIcon: bottomSeries!.length > 1 && indexInBottomConfigs != 0, + showMoveDownIcon: bottomSeries.length > 1 && + indexInBottomConfigs != bottomSeries.length - 1, + showFrame: context.read().chartAxisConfig.showFrame, + ); + + if (isHidden) { + children.add(bottomChart); + continue; + } + + final String key = _panelKeyFor(config, repoIndex); + + // The divider directly above this panel sits between + // `orderedKeys[visiblePosition]` (main, or the previous indicator) + // and `orderedKeys[visiblePosition + 1]` (this indicator) - i.e. + // its divider index within the shared chain is `visiblePosition`. + final int dividerIndex = visiblePosition; + children + ..add( + ResizableChartDivider( + onDragUpdate: (double deltaPixels) => _resizeCascadingPanels( + orderedKeys, + dividerIndex, + deltaPixels / usableHeight, + ), + onDragEnd: _persistPanelFractions, + ), + ) + ..add( + SizedBox( + height: (_panelFractions[key] ?? 0) * usableHeight, + child: bottomChart, ), - ); - final Repository? repository = widget.indicatorsRepo; - - // TODO(Ramin): Use the key (type + number) once it's implemented. - final int indexInBottomConfigs = - referenceIndexOf(widget.bottomConfigs, config); - - final Widget bottomChart = BottomChartMobile( - series: series, - isHidden: repository?.getHiddenStatus(index) ?? false, - granularity: widget.granularity, - pipSize: config.pipSize, - title: - '${config.shortTitle} ${config.number > 0 ? config.number : ''}' - ' (${config.configSummary})', - currentTickAnimationDuration: currentTickAnimationDuration, - quoteBoundsAnimationDuration: quoteBoundsAnimationDuration, - bottomChartTitleMargin: - const EdgeInsets.only(left: Dimens.margin04), - onHideUnhideToggle: () => - _onIndicatorHideToggleTapped(repository, index), - onSwap: (int offset) => _onSwap( - config, widget.bottomConfigs[indexInBottomConfigs + offset]), - showMoveUpIcon: - bottomSeries!.length > 1 && indexInBottomConfigs != 0, - showMoveDownIcon: bottomSeries.length > 1 && - indexInBottomConfigs != bottomSeries.length - 1, - showFrame: context.read().chartAxisConfig.showFrame, ); - return (repository?.getHiddenStatus(index) ?? false) - ? bottomChart - : Expanded( - child: bottomChart, - ); - }).toList(); + visiblePosition++; + } + + return children; + } final List overlaySeries = []; @@ -105,8 +154,24 @@ class _ChartStateMobile extends _ChartState { BuildContext context, BoxConstraints constraints, ) { + final double availableHeight = constraints.maxHeight; + final double mainFraction = + _panelFractions[PanelSizeRepository.mainPanelKey] ?? 1.0; + + // Each divider takes up real space in the bottom section's Column + // alongside the indicator panels, so it must be subtracted from the + // space panel fractions are applied to - otherwise the panels plus + // dividers overflow the section's fixed-height SizedBox below. + final double reservedForDividers = + visibleIndicatorKeys.length * Dimens.chartPanelDividerHitHeight; + final double usableHeight = + _usableHeightFor(availableHeight, visibleIndicatorKeys.length); + final double bottomSectionHeight = + (1 - mainFraction) * usableHeight + reservedForDividers; + final List bottomIndicatorsList = - getBottomIndicatorsList(context); + getBottomIndicatorsList(context, usableHeight); + return Column( children: [ Expanded( @@ -158,18 +223,11 @@ class _ChartStateMobile extends _ChartState { ], ), ), - if (context.read().chartAxisConfig.showFrame && - bottomIndicatorsList.isNotEmpty) - const Divider( - height: 0.5, - thickness: 1, - color: Color(0xFF242828), - ), if (_isAllBottomIndicatorsHidden) ...bottomIndicatorsList else SizedBox( - height: _bottomSectionHeight * constraints.maxHeight, + height: bottomSectionHeight, child: Column(children: bottomIndicatorsList), ), ], diff --git a/lib/src/deriv_chart/chart/chart_state_web.dart b/lib/src/deriv_chart/chart/chart_state_web.dart index ba89e3214..3cf9dd5c1 100644 --- a/lib/src/deriv_chart/chart/chart_state_web.dart +++ b/lib/src/deriv_chart/chart/chart_state_web.dart @@ -1,6 +1,9 @@ part of 'chart.dart'; class _ChartStateWeb extends _ChartState { + String _bottomPanelKey(int index) => + _panelKeyFor(widget.bottomConfigs[index], index); + @override Widget buildChartsLayout( BuildContext context, @@ -14,53 +17,109 @@ class _ChartStateWeb extends _ChartState { widget.quoteBoundsAnimationDuration ?? _defaultDuration; final bool isExpanded = expandedIndex != null; + final int totalBottomCount = widget.bottomConfigs.length; + + // Fractions are tracked for every bottom panel regardless of whether + // it's currently visible, so expanding/collapsing one to fullscreen + // doesn't discard the custom sizes of the others. + final List allPanelKeys = [ + PanelSizeRepository.mainPanelKey, + for (int i = 0; i < totalBottomCount; i++) _bottomPanelKey(i), + ]; + + _syncPanelFractions( + allPanelKeys, + (String key) => key == PanelSizeRepository.mainPanelKey + ? (totalBottomCount > 0 ? 3 / (3 + totalBottomCount) : 1.0) + : 1 / (3 + totalBottomCount), + ); + + final double mainFraction = + _panelFractions[PanelSizeRepository.mainPanelKey] ?? 1.0; + + // While a single bottom panel is expanded to fullscreen, it takes up + // the entire bottom region (instead of its own stored fraction), and + // the main chart keeps the fraction it had before expanding. + double fractionFor(String key) => + isExpanded ? (1 - mainFraction) : (_panelFractions[key] ?? 0); + + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final int dividerCount = isExpanded ? 0 : totalBottomCount; + final double usableHeight = + _usableHeightFor(constraints.maxHeight, dividerCount); + + Widget panelSizedBox(double fraction, Widget child) => SizedBox( + height: fraction * usableHeight, + child: child, + ); - return Column( - children: [ - Expanded( - flex: 3, - child: MainChart( - drawingTools: widget.drawingTools, - controller: _controller, - mainSeries: widget.mainSeries, - overlaySeries: overlaySeries, - annotations: widget.annotations, - markerSeries: widget.markerSeries, - pipSize: widget.pipSize, - onCrosshairAppeared: widget.onCrosshairAppeared, - onQuoteAreaChanged: widget.onQuoteAreaChanged, - isLive: widget.isLive, - showLoadingAnimationForHistoricalData: !widget.dataFitEnabled, - showDataFitButton: - widget.showDataFitButton ?? widget.dataFitEnabled, - showScrollToLastTickButton: - widget.showScrollToLastTickButton ?? true, - opacity: widget.opacity, - chartAxisConfig: widget.chartAxisConfig, - verticalPaddingFraction: widget.verticalPaddingFraction, - showCrosshair: widget.showCrosshair, - onCrosshairDisappeared: widget.onCrosshairDisappeared, - onCrosshairHover: _onCrosshairHover, - loadingAnimationColor: widget.loadingAnimationColor, - currentTickAnimationDuration: currentTickAnimationDuration, - quoteBoundsAnimationDuration: quoteBoundsAnimationDuration, - showCurrentTickBlinkAnimation: - widget.showCurrentTickBlinkAnimation ?? true, - crosshairVariant: widget.crosshairVariant, - interactiveLayerBehaviour: widget.interactiveLayerBehaviour, - useDrawingToolsV2: widget.useDrawingToolsV2, + final List children = [ + panelSizedBox( + mainFraction, + MainChart( + drawingTools: widget.drawingTools, + controller: _controller, + mainSeries: widget.mainSeries, + overlaySeries: overlaySeries, + annotations: widget.annotations, + markerSeries: widget.markerSeries, + pipSize: widget.pipSize, + onCrosshairAppeared: widget.onCrosshairAppeared, + onQuoteAreaChanged: widget.onQuoteAreaChanged, + isLive: widget.isLive, + showLoadingAnimationForHistoricalData: !widget.dataFitEnabled, + showDataFitButton: + widget.showDataFitButton ?? widget.dataFitEnabled, + showScrollToLastTickButton: + widget.showScrollToLastTickButton ?? true, + opacity: widget.opacity, + chartAxisConfig: widget.chartAxisConfig, + verticalPaddingFraction: widget.verticalPaddingFraction, + showCrosshair: widget.showCrosshair, + onCrosshairDisappeared: widget.onCrosshairDisappeared, + onCrosshairHover: _onCrosshairHover, + loadingAnimationColor: widget.loadingAnimationColor, + currentTickAnimationDuration: currentTickAnimationDuration, + quoteBoundsAnimationDuration: quoteBoundsAnimationDuration, + showCurrentTickBlinkAnimation: + widget.showCurrentTickBlinkAnimation ?? true, + crosshairVariant: widget.crosshairVariant, + interactiveLayerBehaviour: widget.interactiveLayerBehaviour, + useDrawingToolsV2: widget.useDrawingToolsV2, + ), ), - ), - if (bottomSeries?.isNotEmpty ?? false) - ...bottomSeries!.mapIndexed((int index, Series series) { - if (isExpanded && expandedIndex != index) { - return const SizedBox.shrink(); - } - - return Expanded( - flex: isExpanded ? bottomSeries.length : 1, - child: BottomChart( - series: series, + ]; + + for (int index = 0; index < totalBottomCount; index++) { + if (isExpanded && expandedIndex != index) { + continue; + } + + final String key = _bottomPanelKey(index); + + // Dragging is only meaningful between panels that are both + // visible with independently-tracked fractions; disable it while + // a single panel is expanded to fullscreen. + if (!isExpanded) { + final int dividerIndex = index; + children.add( + ResizableChartDivider( + onDragUpdate: (double deltaPixels) => _resizeCascadingPanels( + allPanelKeys, + dividerIndex, + deltaPixels / usableHeight, + ), + onDragEnd: _persistPanelFractions, + ), + ); + } + + children.add( + panelSizedBox( + fractionFor(key), + BottomChart( + series: bottomSeries![index], granularity: widget.granularity, pipSize: widget.bottomConfigs[index].pipSize, title: widget.bottomConfigs[index].title, @@ -96,16 +155,19 @@ class _ChartStateWeb extends _ChartState { ), isExpanded: isExpanded, showCrosshair: widget.showCrosshair, - showExpandedIcon: bottomSeries.length > 1, + showExpandedIcon: totalBottomCount > 1, showMoveUpIcon: - !isExpanded && bottomSeries.length > 1 && index != 0, + !isExpanded && totalBottomCount > 1 && index != 0, showMoveDownIcon: !isExpanded && - bottomSeries.length > 1 && - index != bottomSeries.length - 1, + totalBottomCount > 1 && + index != totalBottomCount - 1, ), - ); - }).toList() - ], + ), + ); + } + + return Column(children: children); + }, ); } } diff --git a/lib/src/deriv_chart/chart/panel_size/panel_size_repository.dart b/lib/src/deriv_chart/chart/panel_size/panel_size_repository.dart new file mode 100644 index 000000000..9f6a37b36 --- /dev/null +++ b/lib/src/deriv_chart/chart/panel_size/panel_size_repository.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Persists the relative heights (as fractions of the total available +/// height) of the chart's vertically stacked panels: [MainChart] and each +/// bottom indicator panel. +/// +/// Panels are identified by a string key: `'main'` for the main chart, and +/// `IndicatorConfig.configId` for each bottom indicator panel. +class PanelSizeRepository extends ChangeNotifier { + /// Key of the [MainChart] panel in [fractions]. + static const String mainPanelKey = 'main'; + + String _sharedPrefKey = ''; + + /// Current known fractions, keyed by panel key. + /// + /// Empty until [loadFromPrefs] has completed, or until [save] has been + /// called at least once. + Map fractions = {}; + + SharedPreferences? _prefs; + + /// Storage key of the saved panel fractions. + String get _storageKey => 'panelHeights_$_sharedPrefKey'; + + /// Loads previously saved panel fractions for [symbol] from [prefs]. + void loadFromPrefs(SharedPreferences prefs, String symbol) { + _prefs = prefs; + _sharedPrefKey = symbol; + + final String? encoded = prefs.getString(_storageKey); + + if (encoded == null) { + fractions = {}; + notifyListeners(); + return; + } + + final Map decoded = + jsonDecode(encoded) as Map; + + fractions = decoded.map( + (String key, dynamic value) => MapEntry( + key, + (value as num).toDouble(), + ), + ); + + notifyListeners(); + } + + /// Saves [newFractions] for the current symbol and updates [fractions]. + Future save(Map newFractions) async { + fractions = Map.from(newFractions); + + if (_prefs != null) { + await _prefs!.setString(_storageKey, jsonEncode(fractions)); + } + + notifyListeners(); + } +} diff --git a/lib/src/deriv_chart/chart/resizable_chart_divider.dart b/lib/src/deriv_chart/chart/resizable_chart_divider.dart new file mode 100644 index 000000000..c18de4ebf --- /dev/null +++ b/lib/src/deriv_chart/chart/resizable_chart_divider.dart @@ -0,0 +1,132 @@ +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:deriv_chart/src/theme/dimens.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +/// Width of the visible drag handle pill. +const double _handleWidth = 32; + +/// Height of the visible drag handle pill. +const double _handleHeight = 16; + +/// Width of each of the two bars inside the drag handle. +const double _handleBarWidth = 21.33; + +/// Height of the web hover/drag band around the line - narrower than the +/// touch hit area, since a mouse is precise enough that the cursor should +/// only change (and dragging only start) when it's close to the line and +/// handle, not anywhere within the divider's full row height. This only +/// affects the invisible hit-test region, not the visible line/handle. +const double _webDragBandHeight = 24; + +/// A horizontal divider placed between two vertically stacked chart panels +/// (e.g. between the main chart and a bottom indicator panel, or between two +/// bottom indicator panels). +/// +/// On web, a narrow band around the line is draggable and shows a resize +/// cursor on hover, since mouse pointers are precise. On touch devices, only +/// the drag handle at its horizontal center is draggable, so dragging +/// doesn't start from an accidental tap anywhere along the line. Either way, +/// the line and the handle switch to [ChartTheme.panelDividerActiveColor] +/// only while actively being dragged. +class ResizableChartDivider extends StatefulWidget { + /// Creates a [ResizableChartDivider]. + const ResizableChartDivider({ + required this.onDragUpdate, + this.onDragEnd, + Key? key, + }) : super(key: key); + + /// Called with the vertical drag delta, in logical pixels, as the user + /// drags the divider. + final ValueChanged onDragUpdate; + + /// Called when the drag gesture ends. + final VoidCallback? onDragEnd; + + @override + State createState() => _ResizableChartDividerState(); +} + +class _ResizableChartDividerState extends State { + bool _isDragging = false; + + ChartTheme get _theme => context.read(); + + Widget _dragArea({required Widget child}) => MouseRegion( + cursor: SystemMouseCursors.resizeRow, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onVerticalDragStart: (_) => setState(() => _isDragging = true), + onVerticalDragUpdate: (DragUpdateDetails details) => + widget.onDragUpdate(details.delta.dy), + onVerticalDragEnd: (_) { + setState(() => _isDragging = false); + widget.onDragEnd?.call(); + }, + onVerticalDragCancel: () => setState(() => _isDragging = false), + child: child, + ), + ); + + @override + Widget build(BuildContext context) { + final Color color = _isDragging + ? _theme.panelDividerActiveColor + : _theme.crosshairLineDesktopColor; + + final Widget handle = Container( + width: _handleWidth, + height: _handleHeight, + decoration: BoxDecoration( + color: _theme.backgroundColor, + borderRadius: BorderRadius.circular(Dimens.borderRadius04), + border: Border.all(color: color), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container(width: _handleBarWidth, height: 1, color: color), + const SizedBox(height: 4), + Container(width: _handleBarWidth, height: 1, color: color), + ], + ), + ); + + // The visible line and handle, at their natural size, unaffected by + // however small or large the interactive hit area below is. + final Widget visuals = Stack( + alignment: Alignment.center, + children: [ + Container(height: 1, color: color), + Center(child: handle), + ], + ); + + final Widget hitArea = kIsWeb + ? Center( + child: SizedBox( + height: _webDragBandHeight, + width: double.infinity, + child: _dragArea(child: const SizedBox.expand()), + ), + ) + : Center( + child: SizedBox( + width: 44, + height: Dimens.chartPanelDividerHitHeight, + child: _dragArea(child: const SizedBox.expand()), + ), + ); + + return SizedBox( + height: Dimens.chartPanelDividerHitHeight, + width: double.infinity, + child: Stack( + alignment: Alignment.center, + children: [visuals, hitArea], + ), + ); + } +} diff --git a/lib/src/deriv_chart/deriv_chart.dart b/lib/src/deriv_chart/deriv_chart.dart index 433d8fb49..68643af58 100644 --- a/lib/src/deriv_chart/deriv_chart.dart +++ b/lib/src/deriv_chart/deriv_chart.dart @@ -8,6 +8,7 @@ import 'package:deriv_chart/src/add_ons/extensions.dart'; import 'package:deriv_chart/src/add_ons/repository.dart'; import 'package:deriv_chart/src/deriv_chart/chart/chart.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/annotations/chart_annotation.dart'; +import 'package:deriv_chart/src/deriv_chart/chart/panel_size/panel_size_repository.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/chart_series/data_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/markers/marker_series.dart'; import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/chart_object.dart'; @@ -54,6 +55,7 @@ class DerivChart extends StatefulWidget { this.chartAxisConfig = const ChartAxisConfig(), this.indicatorsRepo, this.drawingToolsRepo, + this.panelSizeRepo, this.drawingTools, this.msPerPx, this.minIntervalWidth, @@ -184,6 +186,10 @@ class DerivChart extends StatefulWidget { /// Chart's drawings final Repository? drawingToolsRepo; + /// Persists the relative sizes of the main chart and bottom indicator + /// panels as the user resizes them. + final PanelSizeRepository? panelSizeRepo; + /// Drawing tools final DrawingTools? drawingTools; @@ -212,6 +218,8 @@ class _DerivChartState extends State { late AddOnsRepository _drawingToolsRepo; + final PanelSizeRepository _panelSizeRepo = PanelSizeRepository(); + final DrawingTools _drawingTools = DrawingTools(); late final InteractiveLayerBehaviour _interactiveLayerBehaviour; @@ -270,6 +278,11 @@ class _DerivChartState extends State { Future loadSavedIndicatorsAndDrawingTools() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); + + if (widget.panelSizeRepo == null) { + _panelSizeRepo.loadFromPrefs(prefs, widget.activeSymbol); + } + final List> _stateRepos = >[_indicatorsRepo, _drawingToolsRepo]; @@ -390,6 +403,7 @@ class _DerivChartState extends State { annotations: widget.annotations, showCrosshair: widget.showCrosshair, indicatorsRepo: widget.indicatorsRepo ?? _indicatorsRepo, + panelSizeRepo: widget.panelSizeRepo ?? _panelSizeRepo, msPerPx: widget.msPerPx, minIntervalWidth: widget.minIntervalWidth, maxIntervalWidth: widget.maxIntervalWidth, diff --git a/lib/src/theme/chart_default_dark_theme.dart b/lib/src/theme/chart_default_dark_theme.dart index 2ffc95268..35834675a 100644 --- a/lib/src/theme/chart_default_dark_theme.dart +++ b/lib/src/theme/chart_default_dark_theme.dart @@ -132,6 +132,9 @@ class ChartDefaultDarkTheme extends ChartDefaultTheme { Color get floatingMenuDragIconColor => DarkThemeColors.floatingMenuDragIconColor; + @override + Color get panelDividerActiveColor => DarkThemeColors.panelDividerActiveColor; + @override Color get lineThicknessDropdownButtonTextColor => DarkThemeColors.lineThicknessDropdownButtonTextColor; diff --git a/lib/src/theme/chart_default_light_theme.dart b/lib/src/theme/chart_default_light_theme.dart index 94930b4ef..07bf962b6 100644 --- a/lib/src/theme/chart_default_light_theme.dart +++ b/lib/src/theme/chart_default_light_theme.dart @@ -132,6 +132,9 @@ class ChartDefaultLightTheme extends ChartDefaultTheme { Color get floatingMenuDragIconColor => LightThemeColors.floatingMenuDragIconColor; + @override + Color get panelDividerActiveColor => LightThemeColors.panelDividerActiveColor; + @override Color get lineThicknessDropdownButtonTextColor => LightThemeColors.lineThicknessDropdownButtonTextColor; diff --git a/lib/src/theme/chart_theme.dart b/lib/src/theme/chart_theme.dart index 89af4df4b..31722c244 100644 --- a/lib/src/theme/chart_theme.dart +++ b/lib/src/theme/chart_theme.dart @@ -94,6 +94,10 @@ abstract class ChartTheme { Color get floatingMenuDragIconColor; + /// Color of a resizable chart panel divider's line and drag handle while + /// it's being dragged. + Color get panelDividerActiveColor; + Color get lineThicknessDropdownButtonTextColor; TextStyle get lineThicknessDropdownButtonTextStyle; diff --git a/lib/src/theme/colors.dart b/lib/src/theme/colors.dart index 4d38d5162..7749d7ac4 100644 --- a/lib/src/theme/colors.dart +++ b/lib/src/theme/colors.dart @@ -110,6 +110,9 @@ class LightThemeColors { static final Color floatingMenuDragIconColor = LightThemeDesignTokens .semanticColorMonochromeTextIconNormalMid; // Hex: #000000 with appropriate opacity + static const Color panelDividerActiveColor = LightThemeDesignTokens + .semanticColorBlueSolidSurfaceNormalHighest; // Hex: #2C9AFF + static const Color lineThicknessDropdownButtonTextColor = ComponentDesignTokens .componentTextIconNormalProminentLight; // Hex: #000000 @@ -245,6 +248,9 @@ class DarkThemeColors { static final Color floatingMenuDragIconColor = DarkThemeDesignTokens .semanticColorMonochromeTextIconNormalMid; // Hex: #000000 with appropriate opacity + static const Color panelDividerActiveColor = DarkThemeDesignTokens + .semanticColorBlueSolidSurfaceNormalHighest; // Hex: #2C9AFF + // Line thickness dropdown colors static const Color lineThicknessDropdownButtonTextColor = ComponentDesignTokens diff --git a/lib/src/theme/dimens.dart b/lib/src/theme/dimens.dart index c38ad2321..ce0be27b1 100644 --- a/lib/src/theme/dimens.dart +++ b/lib/src/theme/dimens.dart @@ -48,4 +48,18 @@ class Dimens { /// Large area line thickness 2 static const double areaLineLargeThickness = 2; + + /// Minimum height a chart panel (main chart or a bottom indicator panel) + /// can be resized down to via [ResizableChartDivider], expressed as a + /// fraction of the total space shared by all panels in its chain rather + /// than a fixed pixel amount. A fixed pixel minimum ends up too large a + /// share of the screen on small devices (e.g. with the maximum of 3 + /// indicators on a short screen); a fixed fraction scales down with the + /// screen instead. + static const double minChartPanelHeightFraction = 0.1; + + /// Height of the hit area for [ResizableChartDivider]. Sized well above + /// its visible line so the drag target stays comfortably tappable on + /// touch devices. + static const double chartPanelDividerHitHeight = 32; } diff --git a/test/deriv_chart/chart/panel_fractions_test.dart b/test/deriv_chart/chart/panel_fractions_test.dart new file mode 100644 index 000000000..ee36d5617 --- /dev/null +++ b/test/deriv_chart/chart/panel_fractions_test.dart @@ -0,0 +1,112 @@ +import 'package:deriv_chart/src/deriv_chart/chart/chart.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('resizeCascadingFractions', () { + // Matches Dimens.minChartPanelHeightFraction. + const double minFraction = 0.1; + + test('shrinks the immediate neighbor when it has room', () { + final Map fractions = { + 'a': 0.4, + 'b': 0.3, + 'c': 0.3, + }; + + final bool changed = resizeCascadingFractions( + fractions, + ['a', 'b', 'c'], + 1, // divider between b and c + -0.1, // grow c, shrink b + ); + + expect(changed, isTrue); + expect(fractions['a'], 0.4); + expect(fractions['b'], closeTo(0.2, 1e-9)); + expect(fractions['c'], closeTo(0.4, 1e-9)); + }); + + test( + 'cascades past a middle panel already at minimum height to a ' + 'further panel with room', () { + final Map fractions = { + 'a': 0.7, // has plenty of room + 'b': minFraction, // already at the minimum + 'c': 0.2, + }; + + final bool changed = resizeCascadingFractions( + fractions, + ['a', 'b', 'c'], + 1, // divider between b and c + -0.1, // try to grow c by 0.1, at b's expense + ); + + expect(changed, isTrue); + // b can't give anything up (already at the minimum), so the shrink + // cascades to a instead; b is unchanged, c gets the full delta. + expect(fractions['a'], closeTo(0.6, 1e-9)); + expect(fractions['b'], closeTo(minFraction, 1e-9)); + expect(fractions['c'], closeTo(0.3, 1e-9)); + }); + + test('clamps to the total room available across the whole donor chain', () { + final Map fractions = { + 'a': 0.15, // only 0.05 of room above the minimum + 'b': minFraction, // already at the minimum + 'c': 0.75, + }; + + final bool changed = resizeCascadingFractions( + fractions, + ['a', 'b', 'c'], + 1, // divider between b and c + -0.2, // ask for more than is available + ); + + expect(changed, isTrue); + expect(fractions['a'], closeTo(minFraction, 1e-9)); + expect(fractions['b'], closeTo(minFraction, 1e-9)); + expect(fractions['c'], closeTo(0.8, 1e-9)); + }); + + test('growing the panel above the divider cascades downward instead', () { + final Map fractions = { + 'a': 0.2, + 'b': minFraction, // already at the minimum + 'c': 0.7, + }; + + final bool changed = resizeCascadingFractions( + fractions, + ['a', 'b', 'c'], + 0, // divider between a and b + 0.1, // grow a, shrink b (then cascade to c) + ); + + expect(changed, isTrue); + expect(fractions['a'], closeTo(0.3, 1e-9)); + expect(fractions['b'], closeTo(minFraction, 1e-9)); + expect(fractions['c'], closeTo(0.6, 1e-9)); + }); + + test('returns false and makes no changes when nothing can be donated', () { + final Map fractions = { + 'a': minFraction, // at the minimum - nothing to donate + 'b': minFraction, // at the minimum - nothing to donate + 'c': 1 - 2 * minFraction, + }; + final Map before = Map.from(fractions); + + final bool changed = resizeCascadingFractions( + fractions, + ['a', 'b', 'c'], + 1, + -0.05, + ); + + expect(changed, isFalse); + expect(fractions, before); + }); + }); +} diff --git a/test/deriv_chart/chart/panel_size_repository_test.dart b/test/deriv_chart/chart/panel_size_repository_test.dart new file mode 100644 index 000000000..cb70db1a3 --- /dev/null +++ b/test/deriv_chart/chart/panel_size_repository_test.dart @@ -0,0 +1,75 @@ +import 'dart:convert'; + +import 'package:deriv_chart/src/deriv_chart/chart/panel_size/panel_size_repository.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + const String symbol = 'R_100'; + final String storageKey = 'panelHeights_$symbol'; + + group('PanelSizeRepository', () { + test('loadFromPrefs with no saved data leaves fractions empty', () async { + SharedPreferences.setMockInitialValues({}); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + + final PanelSizeRepository repo = PanelSizeRepository(); + repo.loadFromPrefs(prefs, symbol); + + expect(repo.fractions, isEmpty); + }); + + test('loadFromPrefs restores previously saved fractions', () async { + SharedPreferences.setMockInitialValues({ + storageKey: jsonEncode({ + PanelSizeRepository.mainPanelKey: 0.6, + 'indicator_1': 0.4, + }), + }); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + + final PanelSizeRepository repo = PanelSizeRepository(); + repo.loadFromPrefs(prefs, symbol); + + expect(repo.fractions[PanelSizeRepository.mainPanelKey], 0.6); + expect(repo.fractions['indicator_1'], 0.4); + }); + + test('save persists fractions and updates the in-memory value', () async { + SharedPreferences.setMockInitialValues({}); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + + final PanelSizeRepository repo = PanelSizeRepository(); + repo.loadFromPrefs(prefs, symbol); + + await repo.save({ + PanelSizeRepository.mainPanelKey: 0.7, + 'indicator_1': 0.3, + }); + + expect(repo.fractions[PanelSizeRepository.mainPanelKey], 0.7); + + // Round-trips through a fresh instance/prefs read. + final PanelSizeRepository reloaded = PanelSizeRepository(); + reloaded.loadFromPrefs(prefs, symbol); + + expect(reloaded.fractions[PanelSizeRepository.mainPanelKey], 0.7); + expect(reloaded.fractions['indicator_1'], 0.3); + }); + + test('fractions are scoped per symbol', () async { + SharedPreferences.setMockInitialValues({ + 'panelHeights_R_100': jsonEncode({'main': 0.6}), + 'panelHeights_R_50': jsonEncode({'main': 0.8}), + }); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + + final PanelSizeRepository repo = PanelSizeRepository(); + repo.loadFromPrefs(prefs, 'R_100'); + expect(repo.fractions['main'], 0.6); + + repo.loadFromPrefs(prefs, 'R_50'); + expect(repo.fractions['main'], 0.8); + }); + }); +} diff --git a/test/deriv_chart/chart/resizable_chart_divider_test.dart b/test/deriv_chart/chart/resizable_chart_divider_test.dart new file mode 100644 index 000000000..fc09b91b0 --- /dev/null +++ b/test/deriv_chart/chart/resizable_chart_divider_test.dart @@ -0,0 +1,64 @@ +import 'package:deriv_chart/src/deriv_chart/chart/resizable_chart_divider.dart'; +import 'package:deriv_chart/src/theme/chart_default_light_theme.dart'; +import 'package:deriv_chart/src/theme/chart_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; + +void main() { + Widget wrap(Widget child) => MaterialApp( + home: Provider.value( + value: ChartDefaultLightTheme(), + child: Scaffold(body: child), + ), + ); + + testWidgets('reports vertical drag delta via onDragUpdate', + (WidgetTester tester) async { + final List deltas = []; + + await tester.pumpWidget( + wrap( + ResizableChartDivider( + onDragUpdate: deltas.add, + ), + ), + ); + + final Offset start = tester.getCenter(find.byType(ResizableChartDivider)); + final TestGesture gesture = await tester.startGesture(start); + await gesture.moveBy(const Offset(0, 20)); + await tester.pump(); + await gesture.up(); + await tester.pump(); + + expect(deltas, isNotEmpty); + expect(deltas.reduce((double a, double b) => a + b), closeTo(20, 0.5)); + }); + + testWidgets('calls onDragEnd when the drag gesture finishes', + (WidgetTester tester) async { + bool dragEnded = false; + + await tester.pumpWidget( + wrap( + ResizableChartDivider( + onDragUpdate: (_) {}, + onDragEnd: () => dragEnded = true, + ), + ), + ); + + final Offset start = tester.getCenter(find.byType(ResizableChartDivider)); + final TestGesture gesture = await tester.startGesture(start); + await gesture.moveBy(const Offset(0, 10)); + await tester.pump(); + + expect(dragEnded, isFalse); + + await gesture.up(); + await tester.pump(); + + expect(dragEnded, isTrue); + }); +} From 7e7b9011dd6b2c35657c834fd7c13cdf73e1e5c8 Mon Sep 17 00:00:00 2001 From: behnam-deriv <133759298+behnam-deriv@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:00:45 +0800 Subject: [PATCH 2/7] fix: persist indicators panel sizes --- .../adx/adx_indicator_config.dart | 3 + .../adx/adx_indicator_config.g.dart | 2 + .../alligator/alligator_indicator_config.dart | 3 + .../alligator_indicator_config.g.dart | 2 + .../aroon/aroon_indicator_config.dart | 3 + .../aroon/aroon_indicator_config.g.dart | 2 + .../awesome_oscillator_indicator_config.dart | 3 + ...awesome_oscillator_indicator_config.g.dart | 2 + .../bollinger_bands_indicator_config.dart | 3 + .../bollinger_bands_indicator_config.g.dart | 2 + .../cci_indicator_config.dart | 3 + .../cci_indicator_config.g.dart | 2 + .../donchian_channel_indicator_config.dart | 3 + .../donchian_channel_indicator_config.g.dart | 2 + .../dpo_indicator/dpo_indicator_config.dart | 3 + .../dpo_indicator/dpo_indicator_config.g.dart | 2 + .../fcb_indicator/fcb_indicator_config.dart | 3 + .../fcb_indicator/fcb_indicator_config.g.dart | 2 + .../gator/gator_indicator_config.dart | 3 + .../gator/gator_indicator_config.g.dart | 2 + .../ichimoku_cloud_indicator_config.dart | 3 + .../ichimoku_cloud_indicator_config.g.dart | 2 + .../indicators_ui/indicator_config.dart | 2 + .../indicators_ui/indicators_dialog.dart | 6 +- .../ma_env_indicator_config.dart | 3 + .../ma_env_indicator_config.g.dart | 2 + .../ma_indicator/ma_indicator_config.dart | 3 + .../ma_indicator/ma_indicator_config.g.dart | 2 + .../macd_indicator/macd_indicator_config.dart | 3 + .../macd_indicator_config.g.dart | 2 + .../parabolic_sar_indicator_config.dart | 3 + .../parabolic_sar_indicator_config.g.dart | 2 + .../rainbow_indicator_config.dart | 3 + .../roc/roc_indicator_config.dart | 3 + .../roc/roc_indicator_config.g.dart | 2 + .../rsi/rsi_indicator_config.dart | 3 + .../rsi/rsi_indicator_config.g.dart | 2 + .../smi/smi_indicator_config.dart | 3 + .../smi/smi_indicator_config.g.dart | 2 + ...tochastic_oscillator_indicator_config.dart | 3 + ...chastic_oscillator_indicator_config.g.dart | 2 + .../williams_r_indicator_config.dart | 3 + .../williams_r_indicator_config.g.dart | 2 + .../zigzag_indicator_config.dart | 3 + .../zigzag_indicator_config.g.dart | 2 + lib/src/deriv_chart/chart/chart.dart | 94 +++++++++++++++---- .../deriv_chart/chart/chart_state_mobile.dart | 4 +- .../deriv_chart/chart/chart_state_web.dart | 2 +- .../panel_size/panel_size_repository.dart | 34 +++++-- lib/src/deriv_chart/deriv_chart.dart | 22 ++++- .../chart/panel_fractions_test.dart | 76 +++++++++++++++ .../chart/panel_size_repository_test.dart | 43 ++++++--- 52 files changed, 348 insertions(+), 43 deletions(-) diff --git a/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart b/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart index 6f81f9050..48302baa2 100644 --- a/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart @@ -33,6 +33,7 @@ class ADXIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : super( isOverlay: false, showLastIndicator: showLastIndicator, @@ -119,6 +120,7 @@ class ADXIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, }) => ADXIndicatorConfig( period: period ?? this.period, @@ -135,5 +137,6 @@ class ADXIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.g.dart index ae57adcc3..7495c6d1c 100644 --- a/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.g.dart @@ -32,11 +32,13 @@ ADXIndicatorConfig _$ADXIndicatorConfigFromJson(Map json) => showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$ADXIndicatorConfigToJson(ADXIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart index b3f0cf051..99259efb0 100644 --- a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart @@ -32,6 +32,7 @@ class AlligatorIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, super.pipSize, }) : super( showLastIndicator: showLastIndicator, @@ -129,6 +130,7 @@ class AlligatorIndicatorConfig extends IndicatorConfig { String? title, int? pipSize, int? number, + String? configId, }) => AlligatorIndicatorConfig( jawPeriod: jawPeriod ?? this.jawPeriod, @@ -145,6 +147,7 @@ class AlligatorIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, pipSize: pipSize ?? this.pipSize, ); } diff --git a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.g.dart index cc3c79527..de10bbc6e 100644 --- a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.g.dart @@ -29,6 +29,7 @@ AlligatorIndicatorConfig _$AlligatorIndicatorConfigFromJson( showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, pipSize: json['pipSize'] as int? ?? 4, ); @@ -36,6 +37,7 @@ Map _$AlligatorIndicatorConfigToJson( AlligatorIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart index 80fac90ea..1aa5f5f9f 100644 --- a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart @@ -25,6 +25,7 @@ class AroonIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : super( isOverlay: false, pipSize: pipSize, @@ -79,6 +80,7 @@ class AroonIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, }) => AroonIndicatorConfig( period: period ?? this.period, @@ -88,5 +90,6 @@ class AroonIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.g.dart index 05f86dcbc..5b965df40 100644 --- a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.g.dart @@ -20,12 +20,14 @@ AroonIndicatorConfig _$AroonIndicatorConfigFromJson( showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$AroonIndicatorConfigToJson( AroonIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.dart b/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.dart index 7e0be4b97..1c67754f1 100644 --- a/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.dart @@ -21,6 +21,7 @@ class AwesomeOscillatorIndicatorConfig extends IndicatorConfig { int pipSize = 4, String? title, super.number, + super.configId, super.showLastIndicator, }) : super( isOverlay: false, @@ -68,6 +69,7 @@ class AwesomeOscillatorIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, int? pipSize, int? number, + String? configId, }) => AwesomeOscillatorIndicatorConfig( barStyle: barStyle ?? this.barStyle, @@ -75,5 +77,6 @@ class AwesomeOscillatorIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, pipSize: pipSize ?? this.pipSize, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.g.dart index a84ae0e7e..296cf92e0 100644 --- a/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/awesome_oscillator/awesome_oscillator_indicator_config.g.dart @@ -15,6 +15,7 @@ AwesomeOscillatorIndicatorConfig _$AwesomeOscillatorIndicatorConfigFromJson( pipSize: json['pipSize'] as int? ?? 4, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, showLastIndicator: json['showLastIndicator'] as bool? ?? false, ); @@ -22,6 +23,7 @@ Map _$AwesomeOscillatorIndicatorConfigToJson( AwesomeOscillatorIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.dart b/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.dart index 6bce1ae01..c00c06432 100644 --- a/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.dart @@ -33,6 +33,7 @@ class BollingerBandsIndicatorConfig extends MAIndicatorConfig { this.showChannelFill = true, bool showLastIndicator = false, super.number, + super.configId, }) : super( period: period, movingAverageType: movingAverageType, @@ -122,6 +123,7 @@ class BollingerBandsIndicatorConfig extends MAIndicatorConfig { bool? showChannelFill, bool? showLastIndicator, int? number, + String? configId, int? pipSize, bool? isOverlay, LineStyle? lineStyle, @@ -140,5 +142,6 @@ class BollingerBandsIndicatorConfig extends MAIndicatorConfig { showChannelFill: showChannelFill ?? this.showChannelFill, showLastIndicator: showLastIndicator ?? this.showLastIndicator, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.g.dart index 48b84b7dc..e1a60897c 100644 --- a/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/bollinger_bands/bollinger_bands_indicator_config.g.dart @@ -30,12 +30,14 @@ BollingerBandsIndicatorConfig _$BollingerBandsIndicatorConfigFromJson( showChannelFill: json['showChannelFill'] as bool? ?? true, showLastIndicator: json['showLastIndicator'] as bool? ?? false, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$BollingerBandsIndicatorConfigToJson( BollingerBandsIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'showLastIndicator': instance.showLastIndicator, 'period': instance.period, 'movingAverageType': diff --git a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart index 1c1b0f646..f56c23ef6 100644 --- a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart @@ -30,6 +30,7 @@ class CCIIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : super( isOverlay: false, pipSize: pipSize, @@ -94,6 +95,7 @@ class CCIIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, }) => CCIIndicatorConfig( period: period ?? this.period, @@ -105,5 +107,6 @@ class CCIIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.g.dart index d9726e729..1d21e04e0 100644 --- a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.g.dart @@ -22,11 +22,13 @@ CCIIndicatorConfig _$CCIIndicatorConfigFromJson(Map json) => showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$CCIIndicatorConfigToJson(CCIIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart index 797ff8d37..2fa7129ec 100644 --- a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart @@ -31,6 +31,7 @@ class DonchianChannelIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, super.pipSize, }) : super( title: title ?? DonchianChannelIndicatorConfig.name, @@ -102,6 +103,7 @@ class DonchianChannelIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, int? pipSize, }) => DonchianChannelIndicatorConfig( @@ -115,6 +117,7 @@ class DonchianChannelIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, pipSize: pipSize ?? this.pipSize, ); } diff --git a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.g.dart index a2683d40c..c773947e8 100644 --- a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.g.dart @@ -27,6 +27,7 @@ DonchianChannelIndicatorConfig _$DonchianChannelIndicatorConfigFromJson( showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, pipSize: json['pipSize'] as int? ?? 4, ); @@ -34,6 +35,7 @@ Map _$DonchianChannelIndicatorConfigToJson( DonchianChannelIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.dart b/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.dart index a2c45ec58..b12d5350f 100644 --- a/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.dart @@ -29,6 +29,7 @@ class DPOIndicatorConfig extends MAIndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : super( period: period, movingAverageType: movingAverageType, @@ -89,6 +90,7 @@ class DPOIndicatorConfig extends MAIndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, int? offset, bool? isOverlay, }) => @@ -102,5 +104,6 @@ class DPOIndicatorConfig extends MAIndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.g.dart index d3f37bebb..5fedc2f55 100644 --- a/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/dpo_indicator/dpo_indicator_config.g.dart @@ -21,11 +21,13 @@ DPOIndicatorConfig _$DPOIndicatorConfigFromJson(Map json) => showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$DPOIndicatorConfigToJson(DPOIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.dart b/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.dart index 1ce2c529f..a80f59ec0 100644 --- a/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.dart @@ -26,6 +26,7 @@ class FractalChaosBandIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, super.pipSize, }) : super( showLastIndicator: showLastIndicator, @@ -81,6 +82,7 @@ class FractalChaosBandIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, int? pipSize, }) => FractalChaosBandIndicatorConfig( @@ -91,6 +93,7 @@ class FractalChaosBandIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, pipSize: pipSize ?? this.pipSize, ); } diff --git a/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.g.dart index 04a7f8835..7a17afecf 100644 --- a/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/fcb_indicator/fcb_indicator_config.g.dart @@ -22,6 +22,7 @@ FractalChaosBandIndicatorConfig _$FractalChaosBandIndicatorConfigFromJson( showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, pipSize: json['pipSize'] as int? ?? 4, ); @@ -29,6 +30,7 @@ Map _$FractalChaosBandIndicatorConfigToJson( FractalChaosBandIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart b/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart index 84fdd420a..f8242ecc5 100644 --- a/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart @@ -28,6 +28,7 @@ class GatorIndicatorConfig extends IndicatorConfig { int pipSize = 4, String? title, super.number, + super.configId, }) : super( isOverlay: false, pipSize: pipSize, @@ -101,6 +102,7 @@ class GatorIndicatorConfig extends IndicatorConfig { int? pipSize, String? title, int? number, + String? configId, bool? showLastIndicator, }) => GatorIndicatorConfig( @@ -114,5 +116,6 @@ class GatorIndicatorConfig extends IndicatorConfig { pipSize: pipSize ?? this.pipSize, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.g.dart index bfff2caae..9f0ce90d5 100644 --- a/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.g.dart @@ -21,12 +21,14 @@ GatorIndicatorConfig _$GatorIndicatorConfigFromJson( pipSize: json['pipSize'] as int? ?? 4, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$GatorIndicatorConfigToJson( GatorIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'pipSize': instance.pipSize, 'jawOffset': instance.jawOffset, diff --git a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart index 2df95c242..f6ff103fb 100644 --- a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart @@ -30,6 +30,7 @@ class IchimokuCloudIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : super( showLastIndicator: showLastIndicator, title: title ?? IchimokuCloudIndicatorConfig.name, @@ -109,6 +110,7 @@ class IchimokuCloudIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, int? pipSize, }) => IchimokuCloudIndicatorConfig( @@ -124,5 +126,6 @@ class IchimokuCloudIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.g.dart index cc0ab46f2..abff27611 100644 --- a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.g.dart @@ -33,12 +33,14 @@ IchimokuCloudIndicatorConfig _$IchimokuCloudIndicatorConfigFromJson( showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$IchimokuCloudIndicatorConfigToJson( IchimokuCloudIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'conversionLinePeriod': instance.conversionLinePeriod, diff --git a/lib/src/add_ons/indicators_ui/indicator_config.dart b/lib/src/add_ons/indicators_ui/indicator_config.dart index 24e9a436c..aa3479b63 100644 --- a/lib/src/add_ons/indicators_ui/indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/indicator_config.dart @@ -39,6 +39,7 @@ abstract class IndicatorConfig extends AddOnConfig { this.showLastIndicator = false, this.pipSize = 4, super.number, + super.configId, }) : super(isOverlay: isOverlay); /// Creates a concrete indicator config from JSON. @@ -152,5 +153,6 @@ abstract class IndicatorConfig extends AddOnConfig { bool? showLastIndicator, int? pipSize, int? number, + String? configId, }); } diff --git a/lib/src/add_ons/indicators_ui/indicators_dialog.dart b/lib/src/add_ons/indicators_ui/indicators_dialog.dart index 14d6d5dca..fcf83b688 100644 --- a/lib/src/add_ons/indicators_ui/indicators_dialog.dart +++ b/lib/src/add_ons/indicators_ui/indicators_dialog.dart @@ -158,7 +158,11 @@ class _IndicatorsDialogState extends State { // the repository itself. final int postFixNumber = repo.getNumberForNewAddOn(config); - repo.add(config.copyWith(number: postFixNumber)); + repo.add(config.copyWith( + number: postFixNumber, + configId: + DateTime.now().millisecondsSinceEpoch.toString(), + )); setState(() {}); } : null, diff --git a/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.dart b/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.dart index 918c17ddc..8031cca21 100644 --- a/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.dart @@ -35,6 +35,7 @@ class MAEnvIndicatorConfig extends MAIndicatorConfig { this.showChannelFill = true, bool showLastIndicator = false, super.number, + super.configId, }) : super( period: period, movingAverageType: movingAverageType, @@ -115,6 +116,7 @@ class MAEnvIndicatorConfig extends MAIndicatorConfig { bool? showChannelFill, bool? showLastIndicator, int? number, + String? configId, int? pipSize, bool? isOverlay, int? offset, @@ -134,5 +136,6 @@ class MAEnvIndicatorConfig extends MAIndicatorConfig { showChannelFill: showChannelFill ?? this.showChannelFill, showLastIndicator: showLastIndicator ?? this.showLastIndicator, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.g.dart index 603282983..358eff7da 100644 --- a/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/ma_env_indicator/ma_env_indicator_config.g.dart @@ -32,12 +32,14 @@ MAEnvIndicatorConfig _$MAEnvIndicatorConfigFromJson( showChannelFill: json['showChannelFill'] as bool? ?? true, showLastIndicator: json['showLastIndicator'] as bool? ?? false, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$MAEnvIndicatorConfigToJson( MAEnvIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'showLastIndicator': instance.showLastIndicator, 'period': instance.period, 'movingAverageType': diff --git a/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.dart b/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.dart index 1a16d7259..633d779dc 100644 --- a/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.dart @@ -28,6 +28,7 @@ class MAIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : period = period ?? 50, movingAverageType = movingAverageType ?? MovingAverageType.simple, fieldType = fieldType ?? 'close', @@ -112,6 +113,7 @@ class MAIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, }) => MAIndicatorConfig( period: period ?? this.period, @@ -124,5 +126,6 @@ class MAIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.g.dart index 4648996d7..58e93698c 100644 --- a/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/ma_indicator/ma_indicator_config.g.dart @@ -21,12 +21,14 @@ MAIndicatorConfig _$MAIndicatorConfigFromJson(Map json) => showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$MAIndicatorConfigToJson(MAIndicatorConfig instance) => { 'isOverlay': instance.isOverlay, 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.dart b/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.dart index 9a9795e68..0221cedc1 100644 --- a/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.dart @@ -29,6 +29,7 @@ class MACDIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : super( isOverlay: false, pipSize: pipSize, @@ -113,6 +114,7 @@ class MACDIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, }) => MACDIndicatorConfig( fastMAPeriod: fastMAPeriod ?? this.fastMAPeriod, @@ -125,5 +127,6 @@ class MACDIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.g.dart index a70ecbb6d..877d672b8 100644 --- a/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/macd_indicator/macd_indicator_config.g.dart @@ -24,12 +24,14 @@ MACDIndicatorConfig _$MACDIndicatorConfigFromJson(Map json) => showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$MACDIndicatorConfigToJson( MACDIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart b/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart index 97f997107..8213a3865 100644 --- a/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart @@ -23,6 +23,7 @@ class ParabolicSARConfig extends IndicatorConfig { this.scatterStyle = const ScatterStyle(), String? title, super.number, + super.configId, }) : super(title: title ?? ParabolicSARConfig.name); /// Initializes from JSON. @@ -70,6 +71,7 @@ class ParabolicSARConfig extends IndicatorConfig { ScatterStyle? scatterStyle, String? title, int? number, + String? configId, bool? showLastIndicator, int? pipSize, }) => @@ -81,5 +83,6 @@ class ParabolicSARConfig extends IndicatorConfig { scatterStyle: scatterStyle ?? this.scatterStyle, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.g.dart index 5f1c38427..082b2e728 100644 --- a/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.g.dart @@ -17,11 +17,13 @@ ParabolicSARConfig _$ParabolicSARConfigFromJson(Map json) => : ScatterStyle.fromJson(json['scatterStyle'] as Map), title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$ParabolicSARConfigToJson(ParabolicSARConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'minAccelerationFactor': instance.minAccelerationFactor, 'maxAccelerationFactor': instance.maxAccelerationFactor, diff --git a/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.dart b/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.dart index 073727f90..041dac648 100644 --- a/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/rainbow_indicator/rainbow_indicator_config.dart @@ -29,6 +29,7 @@ class RainbowIndicatorConfig extends MAIndicatorConfig { this.rainbowLineStyles, bool showLastIndicator = false, super.number, + super.configId, }) : assert( rainbowLineStyles == null || rainbowLineStyles.length == bandsCount, '''If you provide [rainbowLineStyles] it should have the same length as [bandsCount]. @@ -111,6 +112,7 @@ class RainbowIndicatorConfig extends MAIndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, int? bandsCount, }) => RainbowIndicatorConfig( @@ -119,6 +121,7 @@ class RainbowIndicatorConfig extends MAIndicatorConfig { fieldType: fieldType ?? this.fieldType, showLastIndicator: showLastIndicator ?? this.showLastIndicator, number: number ?? this.number, + configId: configId ?? this.configId, bandsCount: bandsCount ?? this.bandsCount, rainbowLineStyles: rainbowLineStyles ?? this.rainbowLineStyles, ); diff --git a/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart b/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart index 2f566f144..a159e3567 100644 --- a/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart @@ -24,6 +24,7 @@ class ROCIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : super( isOverlay: false, pipSize: pipSize, @@ -82,6 +83,7 @@ class ROCIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, }) => ROCIndicatorConfig( period: period ?? this.period, @@ -91,5 +93,6 @@ class ROCIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.g.dart index 7ec5ab725..149d13b11 100644 --- a/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.g.dart @@ -17,11 +17,13 @@ ROCIndicatorConfig _$ROCIndicatorConfigFromJson(Map json) => showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$ROCIndicatorConfigToJson(ROCIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.dart b/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.dart index a0cbad779..e414eeb71 100644 --- a/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.dart @@ -32,6 +32,7 @@ class RSIIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : super( isOverlay: false, pipSize: pipSize, @@ -115,6 +116,7 @@ class RSIIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, }) => RSIIndicatorConfig( period: period ?? this.period, @@ -128,5 +130,6 @@ class RSIIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.g.dart index d993f44aa..18d04a659 100644 --- a/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/rsi/rsi_indicator_config.g.dart @@ -23,11 +23,13 @@ RSIIndicatorConfig _$RSIIndicatorConfigFromJson(Map json) => showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$RSIIndicatorConfigToJson(RSIIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart b/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart index ee7868db2..e80a5ba0b 100644 --- a/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart @@ -39,6 +39,7 @@ class SMIIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : super( isOverlay: false, pipSize: pipSize, @@ -129,6 +130,7 @@ class SMIIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, }) => SMIIndicatorConfig( period: period ?? this.period, @@ -145,5 +147,6 @@ class SMIIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.g.dart index 3baf45a56..a52ff701e 100644 --- a/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.g.dart @@ -33,11 +33,13 @@ SMIIndicatorConfig _$SMIIndicatorConfigFromJson(Map json) => showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$SMIIndicatorConfigToJson(SMIIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart index f670f8f0f..5c703bd93 100644 --- a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart @@ -36,6 +36,7 @@ class StochasticOscillatorIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : super( isOverlay: false, pipSize: pipSize, @@ -123,6 +124,7 @@ class StochasticOscillatorIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, }) => StochasticOscillatorIndicatorConfig( period: period ?? this.period, @@ -140,5 +142,6 @@ class StochasticOscillatorIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart index b7f2bb35d..b99807bbc 100644 --- a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart @@ -35,12 +35,14 @@ StochasticOscillatorIndicatorConfig showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$StochasticOscillatorIndicatorConfigToJson( StochasticOscillatorIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart index c660acde4..64add1115 100644 --- a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart @@ -31,6 +31,7 @@ class WilliamsRIndicatorConfig extends IndicatorConfig { bool showLastIndicator = false, String? title, super.number, + super.configId, }) : super( isOverlay: false, pipSize: pipSize, @@ -102,6 +103,7 @@ class WilliamsRIndicatorConfig extends IndicatorConfig { bool? showLastIndicator, String? title, int? number, + String? configId, }) => WilliamsRIndicatorConfig( period: period ?? this.period, @@ -114,5 +116,6 @@ class WilliamsRIndicatorConfig extends IndicatorConfig { showLastIndicator: showLastIndicator ?? this.showLastIndicator, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.g.dart index c21e3d75b..696241eda 100644 --- a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.g.dart @@ -27,12 +27,14 @@ WilliamsRIndicatorConfig _$WilliamsRIndicatorConfigFromJson( showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$WilliamsRIndicatorConfigToJson( WilliamsRIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'showLastIndicator': instance.showLastIndicator, 'pipSize': instance.pipSize, diff --git a/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart b/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart index 3915114e7..7ae153a8b 100644 --- a/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart @@ -21,6 +21,7 @@ class ZigZagIndicatorConfig extends IndicatorConfig { this.lineStyle = const LineStyle(thickness: 0.9, color: Colors.blue), String? title, super.number, + super.configId, }) : super(title: title ?? ZigZagIndicatorConfig.name); /// Initializes from JSON. @@ -64,6 +65,7 @@ class ZigZagIndicatorConfig extends IndicatorConfig { LineStyle? lineStyle, String? title, int? number, + String? configId, bool? showLastIndicator, int? pipSize, }) => @@ -72,5 +74,6 @@ class ZigZagIndicatorConfig extends IndicatorConfig { lineStyle: lineStyle ?? this.lineStyle, title: title ?? this.title, number: number ?? this.number, + configId: configId ?? this.configId, ); } diff --git a/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.g.dart index 09cfef394..ad93a7a51 100644 --- a/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.g.dart @@ -15,12 +15,14 @@ ZigZagIndicatorConfig _$ZigZagIndicatorConfigFromJson( : LineStyle.fromJson(json['lineStyle'] as Map), title: json['title'] as String?, number: json['number'] as int? ?? 0, + configId: json['configId'] as String?, ); Map _$ZigZagIndicatorConfigToJson( ZigZagIndicatorConfig instance) => { 'number': instance.number, + 'configId': instance.configId, 'title': instance.title, 'distance': instance.distance, 'lineStyle': instance.lineStyle, diff --git a/lib/src/deriv_chart/chart/chart.dart b/lib/src/deriv_chart/chart/chart.dart index 423a8175a..ca1b37cbf 100644 --- a/lib/src/deriv_chart/chart/chart.dart +++ b/lib/src/deriv_chart/chart/chart.dart @@ -49,17 +49,31 @@ const Duration _defaultDuration = Duration(milliseconds: 300); /// panels whose heights add up to the full space they share - e.g. the main /// chart plus every bottom panel, or (on mobile) the individual indicator /// panels sharing the bottom section. +/// +/// `SharedPreferences` access backing [saved] is async, so a chart's very +/// first build always runs before it has actually loaded - every key gets +/// seeded from [defaultFraction] since [saved] is still empty at that point. +/// Set [forceApplySaved] once that load has completed (see +/// [PanelSizeRepository.loadGeneration]) to overwrite already-seeded keys +/// with the real saved values instead of leaving them stuck at the +/// placeholder defaults; leave it `false` on every other build so a value +/// the user is actively resizing isn't clobbered by a stale saved fraction. void syncPanelFractions( Map fractions, List keys, Map saved, - double Function(String key) defaultFraction, -) { + double Function(String key) defaultFraction, { + bool forceApplySaved = false, +}) { final Set keySet = keys.toSet(); fractions.removeWhere((String key, _) => !keySet.contains(key)); for (final String key in keys) { - fractions[key] ??= saved[key] ?? defaultFraction(key); + if (forceApplySaved && saved.containsKey(key)) { + fractions[key] = saved[key]!; + } else { + fractions[key] ??= saved[key] ?? defaultFraction(key); + } } final double sum = fractions.values.fold(0, (double a, double b) => a + b); @@ -317,11 +331,27 @@ abstract class _ChartState extends State with WidgetsBindingObserver { /// [IndicatorConfig.configId] for each bottom indicator panel. final Map _panelFractions = {}; + /// The [PanelSizeRepository.loadGeneration] already applied to + /// [_panelFractions], or `null` if none has been applied yet. See + /// [_syncPanelFractions]. + int? _appliedPanelSizeGeneration; + @override void initState() { super.initState(); WidgetsFlutterBinding.ensureInitialized().addObserver(this); _initChartController(); + widget.panelSizeRepo?.addListener(_onPanelSizeRepoChanged); + } + + /// `PanelSizeRepository.loadFromPrefs` completes asynchronously, so this + /// forces a rebuild once it has (see [_syncPanelFractions]) - otherwise a + /// load that finishes after this chart's first build would silently never + /// reach the screen. + void _onPanelSizeRepoChanged() { + if (mounted) { + setState(() {}); + } } @override @@ -360,16 +390,34 @@ abstract class _ChartState extends State with WidgetsBindingObserver { /// seeding new keys from [widget.panelSizeRepo] (if previously saved) or /// from [defaultFraction], dropping stale keys, and renormalizing so the /// fractions always sum to `1.0`. + /// + /// [widget.panelSizeRepo]'s load is async, so the first call here (during + /// this chart's very first build) always seeds every key from + /// [defaultFraction] since nothing has loaded yet. Once + /// [PanelSizeRepository.loadGeneration] moves past whatever generation was + /// last applied - flagged via [_onPanelSizeRepoChanged] forcing a rebuild - + /// this overwrites those placeholder defaults with the real saved values + /// exactly once per load, instead of leaving them stuck. void _syncPanelFractions( List keys, double Function(String key) defaultFraction, - ) => - syncPanelFractions( - _panelFractions, - keys, - widget.panelSizeRepo?.fractions ?? const {}, - defaultFraction, - ); + ) { + final PanelSizeRepository? repo = widget.panelSizeRepo; + final bool forceApplySaved = repo != null && + repo.loadGeneration > 0 && + repo.loadGeneration != _appliedPanelSizeGeneration; + if (forceApplySaved) { + _appliedPanelSizeGeneration = repo.loadGeneration; + } + + syncPanelFractions( + _panelFractions, + keys, + repo?.fractions ?? const {}, + defaultFraction, + forceApplySaved: forceApplySaved, + ); + } /// Cascading resize of the divider at [dividerIndex] within /// [_panelFractions]. See [resizeCascadingFractions]. @@ -400,12 +448,19 @@ abstract class _ChartState extends State with WidgetsBindingObserver { }); } - /// Key for [config]'s panel within [_panelFractions]/[PanelSizeRepository], - /// falling back to a stable per-index name if it doesn't have a - /// [IndicatorConfig.configId] yet (defensive - repo-managed indicators - /// always get one assigned). - String _panelKeyFor(IndicatorConfig config, int index) => - config.configId ?? 'bottom_$index'; + /// Key for [config]'s panel within [_panelFractions]/[PanelSizeRepository]. + /// + /// Prefers [AddOnConfig.configId] - a fresh id assigned when the indicator + /// is added (see `IndicatorsDialog`'s "Add" handler) - since a positional + /// or title/number-based key would tie a saved size to whatever happens to + /// look the same, meaning deleting an indicator and adding a new one of + /// the same type back could silently inherit the deleted one's size. + /// Falls back to [IndicatorConfig.title] + [AddOnConfig.number] for + /// indicators added without a [configId] (e.g. persisted from before this + /// existed, or added directly by a host app rather than through the + /// indicators dialog). + String _panelKeyFor(IndicatorConfig config) => + config.configId ?? '${config.title}#${config.number}'; /// Height available for panel fractions once [dividerCount] /// [ResizableChartDivider]s - which take up real space in the same @@ -569,6 +624,7 @@ abstract class _ChartState extends State with WidgetsBindingObserver { @override void dispose() { WidgetsFlutterBinding.ensureInitialized().removeObserver(this); + widget.panelSizeRepo?.removeListener(_onPanelSizeRepoChanged); super.dispose(); } @@ -576,6 +632,12 @@ abstract class _ChartState extends State with WidgetsBindingObserver { void didUpdateWidget(covariant Chart oldWidget) { super.didUpdateWidget(oldWidget); + if (widget.panelSizeRepo != oldWidget.panelSizeRepo) { + oldWidget.panelSizeRepo?.removeListener(_onPanelSizeRepoChanged); + widget.panelSizeRepo?.addListener(_onPanelSizeRepoChanged); + _appliedPanelSizeGeneration = null; + } + // if controller is set if (widget.controller != oldWidget.controller) { _initChartController(); diff --git a/lib/src/deriv_chart/chart/chart_state_mobile.dart b/lib/src/deriv_chart/chart/chart_state_mobile.dart index 98eb6338a..6c7880cb4 100644 --- a/lib/src/deriv_chart/chart/chart_state_mobile.dart +++ b/lib/src/deriv_chart/chart/chart_state_mobile.dart @@ -28,7 +28,7 @@ class _ChartStateMobile extends _ChartState { .toList(); final List visibleIndicatorKeys = visibleBottomRepoIndices - .map((int i) => _panelKeyFor(repository!.items[i], i)) + .map((int i) => _panelKeyFor(repository!.items[i])) .toList(); // A single flat, ordered chain covering the main chart and every @@ -101,7 +101,7 @@ class _ChartStateMobile extends _ChartState { continue; } - final String key = _panelKeyFor(config, repoIndex); + final String key = _panelKeyFor(config); // The divider directly above this panel sits between // `orderedKeys[visiblePosition]` (main, or the previous indicator) diff --git a/lib/src/deriv_chart/chart/chart_state_web.dart b/lib/src/deriv_chart/chart/chart_state_web.dart index 3cf9dd5c1..14be0fe31 100644 --- a/lib/src/deriv_chart/chart/chart_state_web.dart +++ b/lib/src/deriv_chart/chart/chart_state_web.dart @@ -2,7 +2,7 @@ part of 'chart.dart'; class _ChartStateWeb extends _ChartState { String _bottomPanelKey(int index) => - _panelKeyFor(widget.bottomConfigs[index], index); + _panelKeyFor(widget.bottomConfigs[index]); @override Widget buildChartsLayout( diff --git a/lib/src/deriv_chart/chart/panel_size/panel_size_repository.dart b/lib/src/deriv_chart/chart/panel_size/panel_size_repository.dart index 9f6a37b36..9d83c0351 100644 --- a/lib/src/deriv_chart/chart/panel_size/panel_size_repository.dart +++ b/lib/src/deriv_chart/chart/panel_size/panel_size_repository.dart @@ -9,11 +9,19 @@ import 'package:shared_preferences/shared_preferences.dart'; /// /// Panels are identified by a string key: `'main'` for the main chart, and /// `IndicatorConfig.configId` for each bottom indicator panel. +/// +/// Deliberately **not** scoped by symbol - unlike indicators or drawing +/// tools, a resized panel layout is a user preference about how they like +/// to view charts in general, not something tied to a particular market, so +/// it stays the same across symbol switches instead of being reloaded (or +/// reset) per symbol. class PanelSizeRepository extends ChangeNotifier { /// Key of the [MainChart] panel in [fractions]. static const String mainPanelKey = 'main'; - String _sharedPrefKey = ''; + /// Storage key of the saved panel fractions. Not symbol-scoped - see the + /// class doc. + static const String _storageKey = 'panelHeights'; /// Current known fractions, keyed by panel key. /// @@ -23,18 +31,29 @@ class PanelSizeRepository extends ChangeNotifier { SharedPreferences? _prefs; - /// Storage key of the saved panel fractions. - String get _storageKey => 'panelHeights_$_sharedPrefKey'; + int _loadGeneration = 0; + + /// Bumped every time [loadFromPrefs] completes (0 means it never has). + /// + /// `SharedPreferences` access is async, so a chart's very first build + /// always happens before this repo has finished loading - it seeds panels + /// with default fractions first, then needs to know when the load has + /// completed so it can re-apply the real saved fractions over those + /// defaults. A plain "have we ever loaded" flag isn't enough for that: it + /// also has to notice a *second* load completing (e.g. if a host app + /// calls [loadFromPrefs] again later), which is what a bumping generation + /// counter - rather than a one-shot bool - is for. + int get loadGeneration => _loadGeneration; - /// Loads previously saved panel fractions for [symbol] from [prefs]. - void loadFromPrefs(SharedPreferences prefs, String symbol) { + /// Loads previously saved panel fractions from [prefs]. + void loadFromPrefs(SharedPreferences prefs) { _prefs = prefs; - _sharedPrefKey = symbol; final String? encoded = prefs.getString(_storageKey); if (encoded == null) { fractions = {}; + _loadGeneration++; notifyListeners(); return; } @@ -49,10 +68,11 @@ class PanelSizeRepository extends ChangeNotifier { ), ); + _loadGeneration++; notifyListeners(); } - /// Saves [newFractions] for the current symbol and updates [fractions]. + /// Saves [newFractions] and updates [fractions]. Future save(Map newFractions) async { fractions = Map.from(newFractions); diff --git a/lib/src/deriv_chart/deriv_chart.dart b/lib/src/deriv_chart/deriv_chart.dart index 68643af58..51523ae52 100644 --- a/lib/src/deriv_chart/deriv_chart.dart +++ b/lib/src/deriv_chart/deriv_chart.dart @@ -274,14 +274,28 @@ class _DerivChartState extends State { loadSavedIndicatorsAndDrawingTools(); }); } + + if (widget.panelSizeRepo == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _loadPanelSizes(); + }); + } } - Future loadSavedIndicatorsAndDrawingTools() async { + /// Loads saved panel sizes, if the host app hasn't supplied its own + /// [PanelSizeRepository]. + /// + /// Deliberately independent of [loadSavedIndicatorsAndDrawingTools] (and + /// its `drawingToolsRepo == null` gating) - a host app can supply its own + /// `indicatorsRepo`/`drawingToolsRepo` while still relying on the default + /// panel-size persistence, and that shouldn't disable panel-size loading. + Future _loadPanelSizes() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); + _panelSizeRepo.loadFromPrefs(prefs); + } - if (widget.panelSizeRepo == null) { - _panelSizeRepo.loadFromPrefs(prefs, widget.activeSymbol); - } + Future loadSavedIndicatorsAndDrawingTools() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); final List> _stateRepos = >[_indicatorsRepo, _drawingToolsRepo]; diff --git a/test/deriv_chart/chart/panel_fractions_test.dart b/test/deriv_chart/chart/panel_fractions_test.dart index ee36d5617..b515a07a7 100644 --- a/test/deriv_chart/chart/panel_fractions_test.dart +++ b/test/deriv_chart/chart/panel_fractions_test.dart @@ -109,4 +109,80 @@ void main() { expect(fractions, before); }); }); + + group('syncPanelFractions', () { + test('seeds new keys from defaultFraction when nothing is saved yet', () { + final Map fractions = {}; + + syncPanelFractions( + fractions, + ['main', 'a'], + const {}, // saved repo hasn't loaded yet + (String key) => key == 'main' ? 0.7 : 0.3, + ); + + expect(fractions['main'], 0.7); + expect(fractions['a'], 0.3); + }); + + test( + 'without forceApplySaved, an already-seeded key is never overwritten ' + 'by a saved value that arrives later', () { + final Map fractions = { + 'main': 0.7, + 'a': 0.3, + }; + + // Simulates the saved fractions finishing their async load only + // after this chart's first build already seeded the defaults above. + syncPanelFractions( + fractions, + ['main', 'a'], + const {'main': 0.4, 'a': 0.6}, + (String key) => key == 'main' ? 0.7 : 0.3, + ); + + expect(fractions['main'], 0.7); + expect(fractions['a'], 0.3); + }); + + test( + 'forceApplySaved overwrites already-seeded keys with the saved ' + 'value - the fix for the load-finishes-after-first-build race', () { + final Map fractions = { + 'main': 0.7, + 'a': 0.3, + }; + + syncPanelFractions( + fractions, + ['main', 'a'], + const {'main': 0.4, 'a': 0.6}, + (String key) => key == 'main' ? 0.7 : 0.3, + forceApplySaved: true, + ); + + expect(fractions['main'], 0.4); + expect(fractions['a'], 0.6); + }); + + test( + 'forceApplySaved leaves a key alone if the saved map has no entry ' + 'for it (e.g. a newly-added panel not yet in storage)', () { + final Map fractions = {'main': 0.5}; + + syncPanelFractions( + fractions, + ['main', 'a'], + const {'main': 0.9}, // no entry for 'a' + (String key) => key == 'main' ? 0.7 : 0.3, + forceApplySaved: true, + ); + + // main:a lands at 0.9:0.3 (a fell back to defaultFraction) before + // being renormalized to sum to 1.0, i.e. a 3:1 ratio. + expect(fractions['main'], closeTo(0.75, 1e-9)); + expect(fractions['a'], closeTo(0.25, 1e-9)); + }); + }); } diff --git a/test/deriv_chart/chart/panel_size_repository_test.dart b/test/deriv_chart/chart/panel_size_repository_test.dart index cb70db1a3..c01faa54b 100644 --- a/test/deriv_chart/chart/panel_size_repository_test.dart +++ b/test/deriv_chart/chart/panel_size_repository_test.dart @@ -5,8 +5,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() { - const String symbol = 'R_100'; - final String storageKey = 'panelHeights_$symbol'; + const String storageKey = 'panelHeights'; group('PanelSizeRepository', () { test('loadFromPrefs with no saved data leaves fractions empty', () async { @@ -14,7 +13,7 @@ void main() { final SharedPreferences prefs = await SharedPreferences.getInstance(); final PanelSizeRepository repo = PanelSizeRepository(); - repo.loadFromPrefs(prefs, symbol); + repo.loadFromPrefs(prefs); expect(repo.fractions, isEmpty); }); @@ -29,7 +28,7 @@ void main() { final SharedPreferences prefs = await SharedPreferences.getInstance(); final PanelSizeRepository repo = PanelSizeRepository(); - repo.loadFromPrefs(prefs, symbol); + repo.loadFromPrefs(prefs); expect(repo.fractions[PanelSizeRepository.mainPanelKey], 0.6); expect(repo.fractions['indicator_1'], 0.4); @@ -40,7 +39,7 @@ void main() { final SharedPreferences prefs = await SharedPreferences.getInstance(); final PanelSizeRepository repo = PanelSizeRepository(); - repo.loadFromPrefs(prefs, symbol); + repo.loadFromPrefs(prefs); await repo.save({ PanelSizeRepository.mainPanelKey: 0.7, @@ -51,25 +50,45 @@ void main() { // Round-trips through a fresh instance/prefs read. final PanelSizeRepository reloaded = PanelSizeRepository(); - reloaded.loadFromPrefs(prefs, symbol); + reloaded.loadFromPrefs(prefs); expect(reloaded.fractions[PanelSizeRepository.mainPanelKey], 0.7); expect(reloaded.fractions['indicator_1'], 0.3); }); - test('fractions are scoped per symbol', () async { + test( + 'fractions are not scoped per symbol - the same saved sizes apply ' + 'regardless of which symbol/market is active', () async { SharedPreferences.setMockInitialValues({ - 'panelHeights_R_100': jsonEncode({'main': 0.6}), - 'panelHeights_R_50': jsonEncode({'main': 0.8}), + storageKey: jsonEncode({'main': 0.6}), }); final SharedPreferences prefs = await SharedPreferences.getInstance(); final PanelSizeRepository repo = PanelSizeRepository(); - repo.loadFromPrefs(prefs, 'R_100'); + repo.loadFromPrefs(prefs); expect(repo.fractions['main'], 0.6); - repo.loadFromPrefs(prefs, 'R_50'); - expect(repo.fractions['main'], 0.8); + // Loading again (e.g. simulating a symbol switch) reads the exact + // same global entry, not a per-symbol one. + repo.loadFromPrefs(prefs); + expect(repo.fractions['main'], 0.6); + }); + + test('loadGeneration starts at 0 and bumps on every loadFromPrefs call', + () async { + SharedPreferences.setMockInitialValues({}); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + + final PanelSizeRepository repo = PanelSizeRepository(); + expect(repo.loadGeneration, 0); + + repo.loadFromPrefs(prefs); + expect(repo.loadGeneration, 1); + + // A later reload bumps it again, so a `Chart` that already applied + // generation 1 knows to re-apply. + repo.loadFromPrefs(prefs); + expect(repo.loadGeneration, 2); }); }); } From af8c46e4faf67f9d18d132ad879730b9d58c0857 Mon Sep 17 00:00:00 2001 From: behnam-deriv <133759298+behnam-deriv@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:07:12 +0800 Subject: [PATCH 3/7] chore: update example app --- example/lib/main.dart | 56 ++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index b23817bcc..678f64a80 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -118,12 +118,22 @@ class _FullscreenChartState extends State { final InteractiveLayerController _interactiveLayerController = InteractiveLayerController(); + // Exposed explicitly (instead of relying on DerivChart's own default + // repo) so the "Add sample indicators" button below can seed a few + // non-overlay indicators with one tap, to quickly try out the resizable + // indicator panel dividers. Not persisted across app restarts. + final AddOnsRepository _indicatorsRepo = + AddOnsRepository( + createAddOn: (Map map) => IndicatorConfig.fromJson(map), + sharedPrefKey: 'example', + ); + late PrefServiceCache _prefService; TradeType _currentTradeType = TradeType.riseFall; // Dynamic marker duration in milliseconds - int _markerDurationMs = 1000 * 5 * 1 * 1; + int _markerDurationMs = 1000 * 20 * 1 * 1; // PnL label lifetime after marker end in milliseconds static const int _pnlLabelLifetimeMs = 4000; @@ -462,6 +472,7 @@ class _FullscreenChartState extends State { is connection_bloc.ConnectionConnectedState, connectionStatus: _buildConnectionStatus(), interactiveLayerController: _interactiveLayerController, + indicatorsRepo: _indicatorsRepo, ), ), _ActionButtonsRow( @@ -471,6 +482,7 @@ class _FullscreenChartState extends State { onUp: () => _addMarker(MarkerDirection.up), onDown: () => _addMarker(MarkerDirection.down), onClearMarkers: () => setState(_clearMarkers), + onAddSampleIndicators: _addSampleBottomIndicators, ), _BarriersControlsRow( onAddVerticalBarrier: () => setState( @@ -618,6 +630,25 @@ class _FullscreenChartState extends State { _tp = false; } + /// Adds a few sample bottom (non-overlay) indicators, so at least two + /// resizable dividers appear between the main chart and the indicator + /// panels without having to add indicators one-by-one via the dialog. + void _addSampleBottomIndicators() { + final Set existingTitles = _indicatorsRepo.items + .map((IndicatorConfig config) => config.title) + .toSet(); + + for (final IndicatorConfig config in const [ + RSIIndicatorConfig(), + MACDIndicatorConfig(), + StochasticOscillatorIndicatorConfig(), + ]) { + if (!existingTitles.contains(config.title)) { + _indicatorsRepo.add(config); + } + } + } + Widget _buildConnectionStatus() => ConnectionStatusLabel( text: _connectionBloc.state is connection_bloc.ConnectionErrorState // ignore: lines_longer_than_80_chars @@ -885,19 +916,6 @@ class _FullscreenChartState extends State { direction: marker.direction, markerType: MarkerType.entrySpot, ), - ChartMarker( - epoch: (endEpoch - marker.epoch) ~/ 2 + marker.epoch, - quote: marker.quote, - direction: marker.direction, - text: '1', - markerType: MarkerType.checkpointLine, - ), - ChartMarker( - epoch: (endEpoch - marker.epoch) ~/ 2 + marker.epoch, - quote: marker.quote, - direction: marker.direction, - markerType: MarkerType.checkpointLineCollapsed, - ), ChartMarker( epoch: endEpoch, quote: marker.quote, @@ -1044,6 +1062,7 @@ class _ChartSection extends StatelessWidget { required this.isConnected, required this.connectionStatus, required this.interactiveLayerController, + required this.indicatorsRepo, }); final InteractiveLayerBehaviour interactiveLayerBehaviour; @@ -1060,6 +1079,7 @@ class _ChartSection extends StatelessWidget { final bool isConnected; final Widget connectionStatus; final InteractiveLayerController interactiveLayerController; + final Repository indicatorsRepo; @override Widget build(BuildContext context) { @@ -1079,6 +1099,7 @@ class _ChartSection extends StatelessWidget { isLive: isLive, opacity: opacity, onVisibleAreaChanged: onVisibleAreaChanged, + indicatorsRepo: indicatorsRepo, ), ), if (!isConnected) @@ -1121,6 +1142,7 @@ class _ActionButtonsRow extends StatelessWidget { required this.onUp, required this.onDown, required this.onClearMarkers, + required this.onAddSampleIndicators, }); final VoidCallback onSettingsPressed; @@ -1129,6 +1151,7 @@ class _ActionButtonsRow extends StatelessWidget { final VoidCallback onUp; final VoidCallback onDown; final VoidCallback onClearMarkers; + final VoidCallback onAddSampleIndicators; @override Widget build(BuildContext context) { @@ -1169,6 +1192,11 @@ class _ActionButtonsRow extends StatelessWidget { icon: const Icon(Icons.delete), onPressed: onClearMarkers, ), + IconButton( + tooltip: 'Add sample indicators (to test resizable panels)', + icon: const Icon(Icons.stacked_line_chart), + onPressed: onAddSampleIndicators, + ), ], ), ); From ed8b4f36939193ee919cf31bf0f71afe6e2e0755 Mon Sep 17 00:00:00 2001 From: behnam-deriv <133759298+behnam-deriv@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:42:10 +0800 Subject: [PATCH 4/7] chore: update chart panel divider UI and improve tap area --- .../chart/resizable_chart_divider.dart | 17 +++-------------- lib/src/theme/dimens.dart | 7 ++++++- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/lib/src/deriv_chart/chart/resizable_chart_divider.dart b/lib/src/deriv_chart/chart/resizable_chart_divider.dart index c18de4ebf..eeb5b7acf 100644 --- a/lib/src/deriv_chart/chart/resizable_chart_divider.dart +++ b/lib/src/deriv_chart/chart/resizable_chart_divider.dart @@ -8,10 +8,7 @@ import 'package:provider/provider.dart'; const double _handleWidth = 32; /// Height of the visible drag handle pill. -const double _handleHeight = 16; - -/// Width of each of the two bars inside the drag handle. -const double _handleBarWidth = 21.33; +const double _handleHeight = 8; /// Height of the web hover/drag band around the line - narrower than the /// touch hit area, since a mouse is precise enough that the cursor should @@ -80,18 +77,10 @@ class _ResizableChartDividerState extends State { width: _handleWidth, height: _handleHeight, decoration: BoxDecoration( - color: _theme.backgroundColor, + color: _isDragging ? color : _theme.backgroundColor, borderRadius: BorderRadius.circular(Dimens.borderRadius04), border: Border.all(color: color), ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container(width: _handleBarWidth, height: 1, color: color), - const SizedBox(height: 4), - Container(width: _handleBarWidth, height: 1, color: color), - ], - ), ); // The visible line and handle, at their natural size, unaffected by @@ -114,7 +103,7 @@ class _ResizableChartDividerState extends State { ) : Center( child: SizedBox( - width: 44, + width: Dimens.chartPanelDividerHandleHitWidth, height: Dimens.chartPanelDividerHitHeight, child: _dragArea(child: const SizedBox.expand()), ), diff --git a/lib/src/theme/dimens.dart b/lib/src/theme/dimens.dart index ce0be27b1..f2d75f37d 100644 --- a/lib/src/theme/dimens.dart +++ b/lib/src/theme/dimens.dart @@ -61,5 +61,10 @@ class Dimens { /// Height of the hit area for [ResizableChartDivider]. Sized well above /// its visible line so the drag target stays comfortably tappable on /// touch devices. - static const double chartPanelDividerHitHeight = 32; + static const double chartPanelDividerHitHeight = 40; + + /// Width of the touch hit area around [ResizableChartDivider]'s drag + /// handle on mobile - wider than the visible handle so it's easy to tap + /// and grab without needing pixel-perfect accuracy. + static const double chartPanelDividerHandleHitWidth = 64; } From 5395fe14d82673e8b0b7eb2d957bacbce175f801 Mon Sep 17 00:00:00 2001 From: behnam-deriv <133759298+behnam-deriv@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:36:38 +0800 Subject: [PATCH 5/7] fix: indicators title bar clipping issue in small panel height --- .../chart/bottom_chart_mobile.dart | 17 +-- lib/src/deriv_chart/chart/chart.dart | 27 ++++- .../deriv_chart/chart/chart_state_mobile.dart | 108 +++++++++--------- lib/src/theme/dimens.dart | 15 +++ 4 files changed, 95 insertions(+), 72 deletions(-) diff --git a/lib/src/deriv_chart/chart/bottom_chart_mobile.dart b/lib/src/deriv_chart/chart/bottom_chart_mobile.dart index 0d303411a..3cf8a2734 100644 --- a/lib/src/deriv_chart/chart/bottom_chart_mobile.dart +++ b/lib/src/deriv_chart/chart/bottom_chart_mobile.dart @@ -80,14 +80,9 @@ class _BottomChartMobileState extends BasicChartState { value: chartConfig, child: ClipRect( child: widget.isHidden - ? Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: _buildCollapsedBottomChart(context), - ), - _buildDivider(), - ], + ? Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: _buildCollapsedBottomChart(context), ) : Stack( children: [ @@ -123,12 +118,6 @@ class _BottomChartMobileState extends BasicChartState { onSwap: widget.onSwap, ); - Widget _buildDivider() => const Divider( - height: 0.5, - thickness: 1, - color: LegacyLightThemeColors.hover, - ); - Widget _buildCollapsedBottomChart(BuildContext context) => Container( alignment: Alignment.topLeft, child: Padding( diff --git a/lib/src/deriv_chart/chart/chart.dart b/lib/src/deriv_chart/chart/chart.dart index ca1b37cbf..cdbaba4e6 100644 --- a/lib/src/deriv_chart/chart/chart.dart +++ b/lib/src/deriv_chart/chart/chart.dart @@ -1,3 +1,5 @@ +import 'dart:math' as math; + import 'package:deriv_chart/src/deriv_chart/chart/data_visualization/models/chart_scale_model.dart'; import 'package:deriv_chart/src/deriv_chart/chart/mobile_chart_frame_dividers.dart'; import 'package:deriv_chart/src/deriv_chart/chart/panel_size/panel_size_repository.dart'; @@ -97,17 +99,30 @@ void syncPanelFractions( /// third panel can shrink the first panel too, once the second has nothing /// left to give) so resizing never gets stuck behind an in-between panel /// that's already at its minimum. Returns whether [fractions] was changed. +/// +/// [usableHeight] converts [Dimens.indicatorTitleBarMinHeight] - the fixed +/// pixel floor a panel is actually rendered at (see +/// `_ChartStateMobile.getBottomIndicatorsList`) - into a fraction, so a +/// donor never gives up more than what its title bar's visible floor +/// already stopped it from losing on screen. Without this, a donor already +/// pinned at that pixel floor would keep silently losing fraction as the +/// drag continued past it - invisibly inflating the recipient without the +/// donor appearing to shrink any further. bool resizeCascadingFractions( Map fractions, List orderedKeys, int dividerIndex, - double deltaFraction, -) { + double deltaFraction, { + double usableHeight = double.infinity, +}) { if (deltaFraction == 0) { return false; } - const double minFraction = Dimens.minChartPanelHeightFraction; + final double minFraction = math.max( + Dimens.minChartPanelHeightFraction, + Dimens.indicatorTitleBarMinHeight / usableHeight, + ); final String recipientKey; final List donorChain; @@ -424,13 +439,15 @@ abstract class _ChartState extends State with WidgetsBindingObserver { void _resizeCascadingPanels( List orderedKeys, int dividerIndex, - double deltaFraction, - ) { + double deltaFraction, { + double usableHeight = double.infinity, + }) { if (resizeCascadingFractions( _panelFractions, orderedKeys, dividerIndex, deltaFraction, + usableHeight: usableHeight, )) { setState(() {}); } diff --git a/lib/src/deriv_chart/chart/chart_state_mobile.dart b/lib/src/deriv_chart/chart/chart_state_mobile.dart index 6c7880cb4..2775678d9 100644 --- a/lib/src/deriv_chart/chart/chart_state_mobile.dart +++ b/lib/src/deriv_chart/chart/chart_state_mobile.dart @@ -23,34 +23,42 @@ class _ChartStateMobile extends _ChartState { if (!repository.items[i].isOverlay) i ]; - final List visibleBottomRepoIndices = bottomRepoIndices - .where((int i) => !repository!.getHiddenStatus(i)) - .toList(); - - final List visibleIndicatorKeys = visibleBottomRepoIndices + // Every bottom indicator's key, visible or hidden, in repo order. + // Syncing/seeding fractions against this full set (rather than just the + // visible ones) is what makes a hidden indicator's fraction survive + // hiding it - since [syncPanelFractions] only drops/renormalizes keys + // that are absent from the list it's given, a hidden indicator's key + // staying in this list keeps its stored fraction untouched until it's + // actually removed from the repo, so unhiding it restores the exact + // size it had before. + final List allBottomIndicatorKeys = bottomRepoIndices .map((int i) => _panelKeyFor(repository!.items[i])) .toList(); - // A single flat, ordered chain covering the main chart and every - // *visible* bottom indicator panel. Keeping this as one chain (rather - // than resizing the bottom section as a whole and then splitting it - // among indicators independently) is what lets a resize cascade past - // an indicator already at its minimum height into the next one that - // still has room, no matter which divider is being dragged. + // A single flat, ordered chain covering the main chart and every bottom + // indicator panel, visible or hidden. Keeping every panel - including + // hidden ones - in this chain (rather than resizing the bottom section + // as a whole and then splitting it among indicators independently) is + // what lets a resize cascade past a panel already at its minimum height + // into the next one that still has room, no matter which divider is + // being dragged, and lets a hidden panel's divider still resize it even + // though its content is collapsed. final List orderedKeys = [ PanelSizeRepository.mainPanelKey, - ...visibleIndicatorKeys, + ...allBottomIndicatorKeys, ]; final double bottomSectionDefaultFraction = _getBottomIndicatorsSectionHeightFraction(bottomRepoIndices.length); _syncPanelFractions( - orderedKeys, + [PanelSizeRepository.mainPanelKey, ...allBottomIndicatorKeys], (String key) => key == PanelSizeRepository.mainPanelKey ? 1 - bottomSectionDefaultFraction : bottomSectionDefaultFraction / - (visibleIndicatorKeys.isEmpty ? 1 : visibleIndicatorKeys.length), + (allBottomIndicatorKeys.isEmpty + ? 1 + : allBottomIndicatorKeys.length), ); List getBottomIndicatorsList( @@ -58,7 +66,7 @@ class _ChartStateMobile extends _ChartState { double usableHeight, ) { final List children = []; - int visiblePosition = 0; + int position = 0; for (final int repoIndex in bottomRepoIndices) { final IndicatorConfig config = repository!.items[repoIndex]; @@ -96,18 +104,34 @@ class _ChartStateMobile extends _ChartState { showFrame: context.read().chartAxisConfig.showFrame, ); - if (isHidden) { - children.add(bottomChart); - continue; - } - final String key = _panelKeyFor(config); + // Every panel - hidden or visible - keeps its own divider and + // reserves the exact height its fraction represents, so hiding one + // only collapses its *content* down to the title bar; its divider + // stays in place and its space keeps being resizable (and cascades + // into neighboring panels the same way a visible one would), so + // nothing shifts or resizes as a side effect of hiding/unhiding it. + // // The divider directly above this panel sits between - // `orderedKeys[visiblePosition]` (main, or the previous indicator) - // and `orderedKeys[visiblePosition + 1]` (this indicator) - i.e. - // its divider index within the shared chain is `visiblePosition`. - final int dividerIndex = visiblePosition; + // `orderedKeys[position]` (main, or the previous indicator) and + // `orderedKeys[position + 1]` (this indicator) - i.e. its divider + // index within the shared chain is `position`. + final int dividerIndex = position; + + // Every panel's title bar (name, hide/unhide, up/down icons) is made + // up of fixed-size text and icons rather than freely-scalable chart + // content, so its fraction - whether it's the *stored* one kept as- + // is while hidden, or one dragged down toward + // [Dimens.minChartPanelHeightFraction] while visible - can still + // work out to less pixel height than that title bar needs. Flooring + // only the rendered height (not the stored fraction) keeps it from + // being clipped without affecting the size a hidden panel is + // restored to on unhide. + final double panelHeight = (_panelFractions[key] ?? 0) * usableHeight; + final double renderedHeight = + math.max(panelHeight, Dimens.indicatorTitleBarMinHeight); + children ..add( ResizableChartDivider( @@ -115,18 +139,19 @@ class _ChartStateMobile extends _ChartState { orderedKeys, dividerIndex, deltaPixels / usableHeight, + usableHeight: usableHeight, ), onDragEnd: _persistPanelFractions, ), ) ..add( SizedBox( - height: (_panelFractions[key] ?? 0) * usableHeight, + height: renderedHeight, child: bottomChart, ), ); - visiblePosition++; + position++; } return children; @@ -155,19 +180,13 @@ class _ChartStateMobile extends _ChartState { BoxConstraints constraints, ) { final double availableHeight = constraints.maxHeight; - final double mainFraction = - _panelFractions[PanelSizeRepository.mainPanelKey] ?? 1.0; // Each divider takes up real space in the bottom section's Column // alongside the indicator panels, so it must be subtracted from the - // space panel fractions are applied to - otherwise the panels plus - // dividers overflow the section's fixed-height SizedBox below. - final double reservedForDividers = - visibleIndicatorKeys.length * Dimens.chartPanelDividerHitHeight; + // space panel fractions are applied to. Every bottom indicator now + // keeps its divider whether hidden or visible, so all of them count. final double usableHeight = - _usableHeightFor(availableHeight, visibleIndicatorKeys.length); - final double bottomSectionHeight = - (1 - mainFraction) * usableHeight + reservedForDividers; + _usableHeightFor(availableHeight, bottomRepoIndices.length); final List bottomIndicatorsList = getBottomIndicatorsList(context, usableHeight); @@ -223,13 +242,7 @@ class _ChartStateMobile extends _ChartState { ], ), ), - if (_isAllBottomIndicatorsHidden) - ...bottomIndicatorsList - else - SizedBox( - height: bottomSectionHeight, - child: Column(children: bottomIndicatorsList), - ), + ...bottomIndicatorsList, ], ); }); @@ -266,17 +279,6 @@ class _ChartStateMobile extends _ChartState { double _getBottomIndicatorsSectionHeightFraction(int bottomIndicatorsCount) => 1 - (0.65 - 0.125 * (bottomIndicatorsCount - 1)); - bool get _isAllBottomIndicatorsHidden { - bool isAllHidden = true; - for (int i = 0; i < widget.indicatorsRepo!.items.length; i++) { - if (!widget.indicatorsRepo!.items[i].isOverlay && - !(widget.indicatorsRepo?.getHiddenStatus(i) ?? false)) { - isAllHidden = false; - } - } - return isAllHidden; - } - Widget _buildOverlayIndicatorsLabels() { final List overlayIndicatorsLabels = []; if (widget.indicatorsRepo != null) { diff --git a/lib/src/theme/dimens.dart b/lib/src/theme/dimens.dart index f2d75f37d..0d15d667e 100644 --- a/lib/src/theme/dimens.dart +++ b/lib/src/theme/dimens.dart @@ -67,4 +67,19 @@ class Dimens { /// handle on mobile - wider than the visible handle so it's easy to tap /// and grab without needing pixel-perfect accuracy. static const double chartPanelDividerHandleHitWidth = 64; + + /// Minimum height of a bottom indicator panel's title bar (its name, + /// hide/unhide, and up/down icons) - enforced regardless of the panel's + /// actual fraction-based height, whether it's hidden (showing only this + /// row, collapsed) or visible but dragged down toward + /// [minChartPanelHeightFraction]. + /// + /// Unlike a panel's chart content - which can render at any height - this + /// row is made up of fixed-size text and icons, so it can't be compressed + /// to fit whatever share of the screen its fraction happens to work out + /// to (e.g. several indicators added on a short screen before any of them + /// have been manually resized, or a visible panel's divider dragged all + /// the way to the fraction minimum). Reserving at least this much space + /// keeps the row from being clipped by its [ClipRect] in those cases. + static const double indicatorTitleBarMinHeight = 44; } From 360bc7498c2e47dc64c539841d7f55a9ca11dc67 Mon Sep 17 00:00:00 2001 From: behnam-deriv <133759298+behnam-deriv@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:53:01 +0800 Subject: [PATCH 6/7] chore: dart format --- .../stochastic_oscillator_indicator_config.g.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart index b99807bbc..97bc1d66c 100644 --- a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart +++ b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.g.dart @@ -35,7 +35,7 @@ StochasticOscillatorIndicatorConfig showLastIndicator: json['showLastIndicator'] as bool? ?? false, title: json['title'] as String?, number: json['number'] as int? ?? 0, - configId: json['configId'] as String?, + configId: json['configId'] as String?, ); Map _$StochasticOscillatorIndicatorConfigToJson( From 6ea95b4754d84910c212beee7c8aeab2a31121b1 Mon Sep 17 00:00:00 2001 From: behnam-deriv <133759298+behnam-deriv@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:47:54 +0800 Subject: [PATCH 7/7] fix: missing config summary for indicators --- lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart | 3 +++ .../indicators_ui/alligator/alligator_indicator_config.dart | 3 +++ .../add_ons/indicators_ui/aroon/aroon_indicator_config.dart | 3 +++ .../commodity_channel_index/cci_indicator_config.dart | 3 +++ .../donchian_channel/donchian_channel_indicator_config.dart | 3 +++ .../add_ons/indicators_ui/gator/gator_indicator_config.dart | 3 +++ .../ichimoku_clouds/ichimoku_cloud_indicator_config.dart | 4 ++++ .../parabolic_sar/parabolic_sar_indicator_config.dart | 3 +++ lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart | 3 +++ lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart | 4 ++++ .../stochastic_oscillator_indicator_config.dart | 3 +++ .../indicators_ui/williams_r/williams_r_indicator_config.dart | 3 +++ .../zigzag_indicator/zigzag_indicator_config.dart | 3 +++ lib/src/deriv_chart/chart/chart_state_mobile.dart | 4 ++-- 14 files changed, 43 insertions(+), 2 deletions(-) diff --git a/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart b/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart index 48302baa2..4217c0d15 100644 --- a/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/adx/adx_indicator_config.dart @@ -83,6 +83,9 @@ class ADXIndicatorConfig extends IndicatorConfig { /// Histogram bar style final BarStyle barStyle; + @override + String get configSummary => '$period, $smoothingPeriod'; + @override Series getSeries(IndicatorInput indicatorInput) => ADXSeries( indicatorInput, diff --git a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart index 99259efb0..cf0b2ad8b 100644 --- a/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/alligator/alligator_indicator_config.dart @@ -83,6 +83,9 @@ class AlligatorIndicatorConfig extends IndicatorConfig { /// Lips line style. final LineStyle lipsLineStyle; + @override + String get configSummary => '$jawPeriod, $teethPeriod, $lipsPeriod'; + @override Series getSeries(IndicatorInput indicatorInput) => AlligatorSeries( indicatorInput, diff --git a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart index 1aa5f5f9f..b32412e16 100644 --- a/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/aroon/aroon_indicator_config.dart @@ -53,6 +53,9 @@ class AroonIndicatorConfig extends IndicatorConfig { /// Down line style. final LineStyle downLineStyle; + @override + String get configSummary => '$period'; + @override Series getSeries(IndicatorInput indicatorInput) => AroonSeries( indicatorInput, diff --git a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart index f56c23ef6..ce52887dc 100644 --- a/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/commodity_channel_index/cci_indicator_config.dart @@ -61,6 +61,9 @@ class CCIIndicatorConfig extends IndicatorConfig { /// Whether to paint overbought/sold zones fill. final bool showZones; + @override + String get configSummary => '$period'; + @override Series getSeries(IndicatorInput indicatorInput) => CCISeries( indicatorInput, diff --git a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart index 2fa7129ec..31860c7ec 100644 --- a/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/donchian_channel/donchian_channel_indicator_config.dart @@ -70,6 +70,9 @@ class DonchianChannelIndicatorConfig extends IndicatorConfig { /// Fill color. final Color fillColor; + @override + String get configSummary => '$highPeriod, $lowPeriod'; + @override Series getSeries(IndicatorInput indicatorInput) => DonchianChannelsSeries.fromIndicator( diff --git a/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart b/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart index f8242ecc5..f5808c1c0 100644 --- a/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/gator/gator_indicator_config.dart @@ -67,6 +67,9 @@ class GatorIndicatorConfig extends IndicatorConfig { /// Histogram bar style final BarStyle barStyle; + @override + String get configSummary => '$jawPeriod, $teethPeriod, $lipsPeriod'; + @override Series getSeries(IndicatorInput indicatorInput) => GatorSeries( indicatorInput, diff --git a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart index f6ff103fb..3eb028f0f 100644 --- a/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/ichimoku_clouds/ichimoku_cloud_indicator_config.dart @@ -74,6 +74,10 @@ class IchimokuCloudIndicatorConfig extends IndicatorConfig { /// Lagging line style. final LineStyle laggingLineStyle; + @override + String get configSummary => + '$conversionLinePeriod, $baseLinePeriod, $spanBPeriod'; + @override Series getSeries(IndicatorInput indicatorInput) => IchimokuCloudSeries(indicatorInput, diff --git a/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart b/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart index 8213a3865..f9583d408 100644 --- a/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/parabolic_sar/parabolic_sar_indicator_config.dart @@ -46,6 +46,9 @@ class ParabolicSARConfig extends IndicatorConfig { /// Scatter points style. final ScatterStyle scatterStyle; + @override + String get configSummary => '$minAccelerationFactor, $maxAccelerationFactor'; + @override Series getSeries(IndicatorInput indicatorInput) => ParabolicSARSeries( indicatorInput, diff --git a/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart b/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart index a159e3567..33df17c7c 100644 --- a/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/roc/roc_indicator_config.dart @@ -52,6 +52,9 @@ class ROCIndicatorConfig extends IndicatorConfig { /// Line style. final LineStyle? lineStyle; + @override + String get configSummary => '$period'; + @override Series getSeries(IndicatorInput indicatorInput) => ROCSeries.fromIndicator( IndicatorConfig.supportedFieldTypes[fieldType]!(indicatorInput), diff --git a/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart b/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart index e80a5ba0b..74a963542 100644 --- a/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/smi/smi_indicator_config.dart @@ -85,6 +85,10 @@ class SMIIndicatorConfig extends IndicatorConfig { /// Signal line style. final LineStyle? signalLineStyle; + @override + String get configSummary => + '$period, $smoothingPeriod, $doubleSmoothingPeriod, $signalPeriod'; + @override Series getSeries(IndicatorInput indicatorInput) => SMISeries( indicatorInput, diff --git a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart index 5c703bd93..ebb8bc974 100644 --- a/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/stochastic_oscillator_indicator/stochastic_oscillator_indicator_config.dart @@ -89,6 +89,9 @@ class StochasticOscillatorIndicatorConfig extends IndicatorConfig { /// Slow line style. final LineStyle slowLineStyle; + @override + String get configSummary => '$period'; + @override Series getSeries(IndicatorInput indicatorInput) => StochasticOscillatorSeries( IndicatorConfig.supportedFieldTypes[fieldType]!(indicatorInput), this, diff --git a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart index 64add1115..244f764c6 100644 --- a/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/williams_r/williams_r_indicator_config.dart @@ -65,6 +65,9 @@ class WilliamsRIndicatorConfig extends IndicatorConfig { /// To show overbought/sold lines and intersection zones with the indicator. final bool showZones; + @override + String get configSummary => '$period'; + @override Series getSeries(IndicatorInput indicatorInput) => WilliamsRSeries( indicatorInput, diff --git a/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart b/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart index 7ae153a8b..e8e60d4b7 100644 --- a/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart +++ b/lib/src/add_ons/indicators_ui/zigzag_indicator/zigzag_indicator_config.dart @@ -41,6 +41,9 @@ class ZigZagIndicatorConfig extends IndicatorConfig { /// ZigZag line style final LineStyle lineStyle; + @override + String get configSummary => '$distance'; + @override Series getSeries(IndicatorInput indicatorInput) => ZigZagSeries( indicatorInput, diff --git a/lib/src/deriv_chart/chart/chart_state_mobile.dart b/lib/src/deriv_chart/chart/chart_state_mobile.dart index 2775678d9..d33bd74e3 100644 --- a/lib/src/deriv_chart/chart/chart_state_mobile.dart +++ b/lib/src/deriv_chart/chart/chart_state_mobile.dart @@ -90,7 +90,7 @@ class _ChartStateMobile extends _ChartState { pipSize: config.pipSize, title: '${config.shortTitle} ${config.number > 0 ? config.number : ''}' - ' (${config.configSummary})', + '${config.configSummary.isEmpty ? '' : ' (${config.configSummary})'}', currentTickAnimationDuration: currentTickAnimationDuration, quoteBoundsAnimationDuration: quoteBoundsAnimationDuration, bottomChartTitleMargin: const EdgeInsets.only(left: Dimens.margin04), @@ -294,7 +294,7 @@ class _ChartStateMobile extends _ChartState { child: IndicatorLabelMobile( title: '${config.shortTitle} ${config.number > 0 ? config.number : ''}' - ' (${config.configSummary})', + '${config.configSummary.isEmpty ? '' : ' (${config.configSummary})'}', showMoveUpIcon: false, showMoveDownIcon: false, isHidden: widget.indicatorsRepo?.getHiddenStatus(i) ?? false,