Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:deriv_chart/src/models/chart_config.dart';
import 'package:deriv_chart/src/theme/chart_theme.dart';
import 'package:deriv_chart/src/theme/colors.dart';
import 'package:deriv_chart/src/theme/dimens.dart';
import 'package:deriv_chart/src/theme/text_styles.dart';
import 'package:deriv_chart/src/widgets/bottom_indicator_title.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
Expand All @@ -15,10 +14,12 @@ import 'data_visualization/chart_series/series.dart';
import 'indicator_label_icons.dart';
import 'x_axis/x_axis_model.dart';

/// Mobile version of the chart to add the bottom indicators too.
class BottomChartMobile extends BasicChart {
/// Initializes a bottom chart mobile.
const BottomChartMobile({
/// A bottom indicator's panel, rendered together with its [IndicatorLabel].
///
/// Used by both platforms - the label and its actions are identical on each.
class BottomChartWithLabel extends BasicChart {
/// Initializes a bottom indicator panel with its label.
const BottomChartWithLabel({
required Series series,
required this.granularity,
required this.title,
Expand Down Expand Up @@ -85,10 +86,10 @@ class BottomChartMobile extends BasicChart {
final IndicatorLabelIcons icons;

@override
_BottomChartMobileState createState() => _BottomChartMobileState();
_BottomChartWithLabelState createState() => _BottomChartWithLabelState();
}

class _BottomChartMobileState extends BasicChartState<BottomChartMobile> {
class _BottomChartWithLabelState extends BasicChartState<BottomChartWithLabel> {
ChartTheme get theme => context.read<ChartTheme>();

@override
Expand All @@ -113,7 +114,7 @@ class _BottomChartMobileState extends BasicChartState<BottomChartMobile> {
Positioned(
top: 4,
left: widget.bottomChartTitleMargin?.left ?? 10,
child: _buildIndicatorLabelMobile(),
child: _buildIndicatorLabel(),
)
],
),
Expand All @@ -131,7 +132,7 @@ class _BottomChartMobileState extends BasicChartState<BottomChartMobile> {
),
);

Widget _buildIndicatorLabelMobile() => IndicatorLabelMobile(
Widget _buildIndicatorLabel() => IndicatorLabel(
title: widget.title,
isExpanded: widget.isExpanded,
showMoveUpIcon: widget.showMoveUpIcon,
Expand All @@ -151,12 +152,12 @@ class _BottomChartMobileState extends BasicChartState<BottomChartMobile> {
padding: EdgeInsets.only(
left: widget.bottomChartTitleMargin?.left ?? 10,
),
child: _buildIndicatorLabelMobile(),
child: _buildIndicatorLabel(),
),
);

@override
void didUpdateWidget(BottomChartMobile oldChart) {
void didUpdateWidget(BottomChartWithLabel oldChart) {
super.didUpdateWidget(oldChart);

xAxis.update(
Expand All @@ -179,9 +180,9 @@ class _BottomChartMobileState extends BasicChartState<BottomChartMobile> {
/// Expanding/collapsing only affects which action buttons are shown; it never
/// hides the indicator's data - that is controlled independently by the eye
/// (hide/unhide) button.
class IndicatorLabelMobile extends StatelessWidget {
class IndicatorLabel extends StatelessWidget {
/// Initializes a bottom chart indicator label.
const IndicatorLabelMobile({
const IndicatorLabel({
required this.title,
required this.isExpanded,
required this.showMoveUpIcon,
Expand Down Expand Up @@ -256,13 +257,7 @@ class IndicatorLabelMobile extends StatelessWidget {
behavior: HitTestBehavior.opaque,
child: BottomIndicatorTitle(
title,
theme.textStyle(
color: theme.base01Color,
textStyle: theme.textStyle(
textStyle: TextStyles.caption,
color: theme.base01Color,
),
),
theme.indicatorLabelTextStyle,
),
),
// The action buttons slide in/out horizontally as the label is
Expand Down Expand Up @@ -346,7 +341,7 @@ class IndicatorLabelMobile extends StatelessWidget {
turns: isExpanded ? 0.5 : 0.0,
child: Icon(
icons.expandCollapse,
size: 16,
size: context.read<ChartTheme>().indicatorLabelIconSize,
color: context.read<ChartTheme>().base01Color,
),
),
Expand All @@ -373,7 +368,7 @@ class IndicatorLabelMobile extends StatelessWidget {
tapTargetSize: MaterialTapTargetSize.shrinkWrap),
icon: Icon(
iconData,
size: 16,
size: context.read<ChartTheme>().indicatorLabelIconSize,
color: context.read<ChartTheme>().base01Color,
),
onPressed: onPressed,
Expand Down
112 changes: 94 additions & 18 deletions lib/src/deriv_chart/chart/chart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ import '../../models/tick.dart';
import '../../theme/chart_default_dark_theme.dart';
import '../../theme/chart_theme.dart';
import '../interactive_layer/interactive_layer_behaviours/interactive_layer_behaviour.dart';
import 'bottom_chart.dart';
import 'bottom_chart_mobile.dart';
import 'bottom_chart_with_label.dart';
import 'indicator_label_icons.dart';
import 'data_visualization/annotations/chart_annotation.dart';
import 'data_visualization/chart_data.dart';
Expand Down Expand Up @@ -332,8 +331,7 @@ class Chart extends StatefulWidget {
/// Icons used by the on-chart indicator labels (eye, reorder arrows,
/// settings, delete and the expand/collapse chevron).
///
/// Any icon left unset falls back to its Material default. Currently applied
/// on mobile.
/// Any icon left unset falls back to its Material default.
final IndicatorLabelIcons? indicatorLabelIcons;

@override
Expand All @@ -348,7 +346,14 @@ abstract class _ChartState extends State<Chart> with WidgetsBindingObserver {
late ChartController _controller;
late ChartTheme _chartTheme;
late List<Series>? bottomSeries;
int? expandedIndex;

/// Panel keys (see [_panelKeyFor]) of indicator labels currently expanded to
/// show their action buttons. Keyed by panel key - rather than held as local
/// widget state - so an indicator's expanded/collapsed state follows it
/// across reorders, hides and the frequent live-tick rebuilds, and never
/// gets attached to the wrong indicator. Labels default to collapsed (absent
/// from this set).
final Set<String> _expandedLabelKeys = <String>{};

/// Current fraction of the available height occupied by each chart panel,
/// keyed by [PanelSizeRepository.mainPanelKey] for the main chart and by
Expand Down Expand Up @@ -495,6 +500,90 @@ abstract class _ChartState extends State<Chart> with WidgetsBindingObserver {
(totalHeight - dividerCount * Dimens.chartPanelDividerHitHeight)
.clamp(0.0, double.infinity);

/// Index of [element] within [list] by identity rather than equality -
/// indicator configs of the same type with the same settings compare equal,
/// so `indexOf` would find the wrong one.
int referenceIndexOf(List<dynamic> list, dynamic element) {
for (int i = 0; i < list.length; i++) {
if (identical(list[i], element)) {
return i;
}
}
return -1;
}

/// Whether [config]'s label is currently showing its action buttons.
bool _isLabelExpanded(IndicatorConfig config) =>
_expandedLabelKeys.contains(_panelKeyFor(config));

void _toggleLabelExpanded(IndicatorConfig config) {
final String key = _panelKeyFor(config);
setState(() {
if (!_expandedLabelKeys.remove(key)) {
_expandedLabelKeys.add(key);
}
});
}

/// The indicator-label icons supplied by the host app, or Material defaults.
IndicatorLabelIcons get _labelIcons =>
widget.indicatorLabelIcons ?? const IndicatorLabelIcons();

void _onIndicatorHideToggleTapped(
Repository<IndicatorConfig>? repository,
int index,
) {
repository?.updateHiddenStatus(
index: index,
hidden: !repository.getHiddenStatus(index),
);
}

/// The title shown on an indicator's label - its short name, the instance
/// number once there is more than one of a type, and its settings summary.
String _indicatorLabelTitle(IndicatorConfig config) =>
'${config.shortTitle} ${config.number > 0 ? config.number : ''}'
'${config.configSummary.isEmpty ? '' : ' (${config.configSummary})'}';

/// Labels for the overlay indicators drawn on the main chart, stacked at its
/// top-left. Bottom indicators carry their own label inside their panel.
Widget _buildOverlayIndicatorsLabels() {
final List<Widget> overlayIndicatorsLabels = <Widget>[];
if (widget.indicatorsRepo != null) {
for (int i = 0; i < widget.indicatorsRepo!.items.length; i++) {
final IndicatorConfig config = widget.indicatorsRepo!.items[i];
if (!config.isOverlay) {
continue;
}

overlayIndicatorsLabels.add(
Padding(
padding: const EdgeInsets.only(bottom: Dimens.margin04),
child: IndicatorLabel(
title: _indicatorLabelTitle(config),
isExpanded: _isLabelExpanded(config),
showMoveUpIcon: false,
showMoveDownIcon: false,
isHidden: widget.indicatorsRepo?.getHiddenStatus(i) ?? false,
icons: _labelIcons,
onExpandToggle: () => _toggleLabelExpanded(config),
onHideUnhideToggle: () {
_onIndicatorHideToggleTapped(widget.indicatorsRepo, i);
},
onEdit: () => _onEdit(config),
onRemove: () => _onRemove(config),
),
),
);
}
}

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: overlayIndicatorsLabels,
);
}

void _onCrosshairHover(
Offset globalPosition,
Offset localPosition,
Expand Down Expand Up @@ -599,8 +688,6 @@ abstract class _ChartState extends State<Chart> with WidgetsBindingObserver {
}

void _onRemove(IndicatorConfig config) {
expandedIndex = null;

if (widget.indicatorsRepo != null) {
final int index = widget.indicatorsRepo!.items.indexOf(config);
widget.indicatorsRepo!.removeAt(index);
Expand Down Expand Up @@ -681,16 +768,5 @@ abstract class _ChartState extends State<Chart> with WidgetsBindingObserver {
_controller.onScrollToLastTick?.call(animate: false);
}
}

// Check if the the expanded bottom indicator is moved/removed.
if (expandedIndex != null &&
oldWidget.bottomConfigs.length != widget.bottomConfigs.length &&
expandedIndex! < (oldWidget.bottomConfigs.length)) {
final int? newIndex =
widget.bottomConfigs.indexOf(oldWidget.bottomConfigs[expandedIndex!]);
if (newIndex != expandedIndex) {
expandedIndex = newIndex == -1 ? null : newIndex;
}
}
}
}
88 changes: 2 additions & 86 deletions lib/src/deriv_chart/chart/chart_state_mobile.dart
Original file line number Diff line number Diff line change
@@ -1,30 +1,6 @@
part of 'chart.dart';

class _ChartStateMobile extends _ChartState {
/// Panel keys (see [_panelKeyFor]) of indicator labels currently expanded to
/// show their action buttons. Keyed by panel key - rather than held as local
/// widget state - so an indicator's expanded/collapsed state follows it
/// across reorders, hides and the frequent live-tick rebuilds, and never
/// gets attached to the wrong indicator. Labels default to collapsed (absent
/// from this set).
final Set<String> _expandedLabelKeys = <String>{};

bool _isLabelExpanded(IndicatorConfig config) =>
_expandedLabelKeys.contains(_panelKeyFor(config));

void _toggleLabelExpanded(IndicatorConfig config) {
final String key = _panelKeyFor(config);
setState(() {
if (!_expandedLabelKeys.remove(key)) {
_expandedLabelKeys.add(key);
}
});
}

/// The indicator-label icons supplied by the host app, or Material defaults.
IndicatorLabelIcons get _labelIcons =>
widget.indicatorLabelIcons ?? const IndicatorLabelIcons();

@override
Widget buildChartsLayout(
BuildContext context,
Expand Down Expand Up @@ -107,15 +83,13 @@ class _ChartStateMobile extends _ChartState {
final int indexInBottomConfigs =
referenceIndexOf(widget.bottomConfigs, config);

final Widget bottomChart = BottomChartMobile(
final Widget bottomChart = BottomChartWithLabel(
series: series,
isHidden: isHidden,
isExpanded: _isLabelExpanded(config),
granularity: widget.granularity,
pipSize: config.pipSize,
title:
'${config.shortTitle} ${config.number > 0 ? config.number : ''}'
'${config.configSummary.isEmpty ? '' : ' (${config.configSummary})'}',
title: _indicatorLabelTitle(config),
currentTickAnimationDuration: currentTickAnimationDuration,
quoteBoundsAnimationDuration: quoteBoundsAnimationDuration,
bottomChartTitleMargin: const EdgeInsets.only(left: Dimens.margin04),
Expand Down Expand Up @@ -286,64 +260,6 @@ class _ChartStateMobile extends _ChartState {
),
);

int referenceIndexOf(List<dynamic> list, dynamic element) {
for (int i = 0; i < list.length; i++) {
if (identical(list[i], element)) {
return i;
}
}
return -1;
}

void _onIndicatorHideToggleTapped(
Repository<IndicatorConfig>? repository,
int index,
) {
repository?.updateHiddenStatus(
index: index,
hidden: !repository.getHiddenStatus(index),
);
}

double _getBottomIndicatorsSectionHeightFraction(int bottomIndicatorsCount) =>
1 - (0.65 - 0.125 * (bottomIndicatorsCount - 1));

Widget _buildOverlayIndicatorsLabels() {
final List<Widget> overlayIndicatorsLabels = <Widget>[];
if (widget.indicatorsRepo != null) {
for (int i = 0; i < widget.indicatorsRepo!.items.length; i++) {
final IndicatorConfig config = widget.indicatorsRepo!.items[i];
if (!config.isOverlay) {
continue;
}

overlayIndicatorsLabels.add(
Padding(
padding: const EdgeInsets.only(bottom: Dimens.margin04),
child: IndicatorLabelMobile(
title:
'${config.shortTitle} ${config.number > 0 ? config.number : ''}'
'${config.configSummary.isEmpty ? '' : ' (${config.configSummary})'}',
isExpanded: _isLabelExpanded(config),
showMoveUpIcon: false,
showMoveDownIcon: false,
isHidden: widget.indicatorsRepo?.getHiddenStatus(i) ?? false,
icons: _labelIcons,
onExpandToggle: () => _toggleLabelExpanded(config),
onHideUnhideToggle: () {
_onIndicatorHideToggleTapped(widget.indicatorsRepo, i);
},
onEdit: () => _onEdit(config),
onRemove: () => _onRemove(config),
),
),
);
}
}

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: overlayIndicatorsLabels,
);
}
}
Loading
Loading