Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(deriv_mobile_chart_wrapper): Add Indicator bottom sheet and categories_ #683

Merged
merged 14 commits into from
Jul 18, 2024
Merged
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/deriv_mobile_chart_wrapper/lib/src/assets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ const String rsiIcon = '${iconAssetsFolder}ic_rsi.svg';
const String bollingerBandsIcon = '${iconAssetsFolder}ic_bollinger_bands.svg';
const String movingAverageIcon = '${iconAssetsFolder}ic_moving_average.svg';
const String indicatorsMenuIcon = '${iconAssetsFolder}ic_indicators_menu.svg';
const String emptyStateIndicatorsIcon =
'${iconAssetsFolder}ic_indicators_empty_state.svg';
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:deriv_theme/deriv_theme.dart';

import 'custom_chip.dart';

/// A widget to show a list of `CustomChip` widgets.
class ChipsList extends StatelessWidget {
/// Constructor of the widget
const ChipsList({
required this.items,
this.isHorizontalPaddingEnabled = false,
double? horizontalPadding,
Key? key,
}) : horizontalPadding = isHorizontalPaddingEnabled
? (horizontalPadding ?? ThemeProvider.margin16)
: ThemeProvider.zeroMargin,
super(key: key);

/// The list of the items.
final List<CustomChip> items;

/// If true enable a padding at the start and end of the list.
/// Default value is false.
final bool isHorizontalPaddingEnabled;

/// The padding value for the horizontal padding.
/// If [isHorizontalPaddingEnabled] is true and [horizontalPadding] is null
/// then [ThemeProvider.margin16] will be used as default value.
/// Otherwise [ThemeProvider.zeroMargin] will be used as default value.
final double horizontalPadding;

@override
Widget build(BuildContext context) => SizedBox(
height: ThemeProvider.margin36,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: items.length,
itemBuilder: (_, int index) {
final double leftPadding =
index == 0 ? horizontalPadding : ThemeProvider.zeroMargin;
final double rightPadding = index == items.length - 1
? horizontalPadding
: ThemeProvider.margin08;
ramin-deriv marked this conversation as resolved.
Show resolved Hide resolved

return Padding(
padding: EdgeInsets.only(left: leftPadding, right: rightPadding),
child: items[index],
);
},
),
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'custom_chip.dart';
export 'chips_list.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import 'package:flutter/material.dart';

import 'package:deriv_theme/deriv_theme.dart';

/// The type of function to be passed to [CustomChip]'s `onTap` property.
typedef OnTapCustomChip<T> = void Function(T? value, String? title);

/// Will be called to get the content that needs to be shown inside chips.
typedef LabelBuilder<T> = String Function(T? value, String? title);

/// Will be called to get the content that needs to be shown inside chips.
typedef LabelWidgetBuilder<T> = Widget Function(T? value, String? title);

/// A Custom chip with a disabled and enabled design based on [isSelected].
class CustomChip<T> extends StatelessWidget {
/// Initializes a [CustomChip] widget.
const CustomChip({
this.value,
this.labelBuilder,
this.labelWidgetBuilder,
this.title,
this.isSelected = true,
this.textStyle,
this.onTap,
this.borderRadius = ThemeProvider.borderRadius04,
this.activeBackgroundColor,
Key? key,
}) : assert(
value != null || labelWidgetBuilder != null,
'Both value and labelWidgetBuilder cannot be null at the same time.',
),
super(key: key);

/// Whether the chip is displayed as selected or not.
final bool isSelected;

/// The title text to be shown inside of the chip.
final String? title;

/// The value text to be shown inside of the chip.
final T? value;

/// To get the content to be shown inside chips.
final LabelBuilder<T>? labelBuilder;

/// To get the widget to be shown inside chips.
final LabelWidgetBuilder<T>? labelWidgetBuilder;

/// Called when a custom chip is tapped.
/// Pass [onTap] as null to disable the functionality of the chips.
final OnTapCustomChip<T>? onTap;

/// The border radius of chips container.
final double borderRadius;

/// Container background color when [isSelected] is true.
///
/// If null then [base6] apply as container background.
final Color? activeBackgroundColor;

/// TextStyle of the chip title.
final TextStyle? textStyle;

@override
Widget build(BuildContext context) => TextButton(
style: TextButton.styleFrom(
backgroundColor: _backgroundColor(context),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(borderRadius),
),
),
),
onPressed: onTap == null ? null : () => onTap?.call(value, title),
child: labelWidgetBuilder?.call(value, title) ??
Text(
labelBuilder?.call(value, title) ??
"${title ?? ''}${title == null ? '' : ': '}$value",
style: context.theme.textStyle(
textStyle: textStyle ?? TextStyles.body1,
color: _textColor(context),
),
),
);

Color _backgroundColor(BuildContext context) => isSelected
? activeBackgroundColor ?? context.theme.colors.active
: context.theme.colors.secondary;

Color _textColor(BuildContext context) => isSelected
? context.theme.colors.prominent
: context.theme.colors.lessProminent;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import 'package:flutter/material.dart';

import 'package:deriv_theme/deriv_theme.dart';

/// This widget displays a widget with the possibility of showing a badge icon
/// with a count value on the widget
class DerivBadge extends StatelessWidget {
/// Initializes the widget.
const DerivBadge({
super.key,
this.child,
this.count,
this.enabled = true,
this.alignment = Alignment.topRight,
});

/// The widget that is going to be displayed.
final Widget? child;

/// Is the badge of the widget is going to be displayed or not
///
/// Default is true.
final bool enabled;

/// Displays a count value in the badge widget.
final int? count;

/// The position of the badge.
///
/// Default is [Alignment.topRight].
final Alignment alignment;

@override
Widget build(BuildContext context) {
final bool showDot = count == null;
final bool hasCount = !showDot && count! > 0;

return Stack(
alignment: alignment,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(ThemeProvider.margin04),
child: child ?? const SizedBox(),
),
Visibility(
visible: enabled,
child: Container(
decoration: showDot || hasCount
? BoxDecoration(
shape: BoxShape.circle,
color: context.theme.colors.danger,
)
: const BoxDecoration(),
constraints: BoxConstraints(
minWidth:
showDot ? ThemeProvider.margin12 : ThemeProvider.margin16,
minHeight:
showDot ? ThemeProvider.margin12 : ThemeProvider.margin16,
),
child: hasCount
? SizedBox(
width: ThemeProvider.margin16,
height: ThemeProvider.margin16,
child: Center(
child: Text(
'$count',
textAlign: TextAlign.center,
style: context.theme.textStyle(
textStyle: TextStyles.badgeCounter,
),
),
),
)
: const SizedBox.shrink(),
),
),
],
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:deriv_theme/deriv_theme.dart';

/// Information banner used to display information to the user.
class InfoBanner extends StatelessWidget {
/// Initializes [InfoBanner].
const InfoBanner({
required this.message,
this.onClose,
Key? key,
}) : super(key: key);

/// Message to be displayed.
final String message;

/// This callback will be called when the user click on the close banner icon.
final VoidCallback? onClose;

@override
Widget build(BuildContext context) => Container(
padding: const EdgeInsets.symmetric(
horizontal: ThemeProvider.margin16,
vertical: ThemeProvider.margin08,
),
decoration: BoxDecoration(
color: context.theme.colors.information.withOpacity(0.24),
borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(
Icons.info_outline,
color: context.theme.colors.information,
size: ThemeProvider.iconSize24,
),
const SizedBox(width: ThemeProvider.margin08),
Expanded(
child: Text(
message,
style: TextStyles.overline,
),
),
if (onClose != null)
Padding(
padding: const EdgeInsetsDirectional.only(
start: ThemeProvider.margin08,
),
child: GestureDetector(
onTap: onClose,
child: Icon(
Icons.close,
color: context.theme.colors.prominent,
size: ThemeProvider.iconSize16,
),
),
),
],
),
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';

/// A custom scroll behavior that removes the glowing effect when scrolling.
class NoGlowScrollBehavior extends ScrollBehavior {
@override
Widget buildOverscrollIndicator(
BuildContext context, Widget child, ScrollableDetails details) {
return child; // This effectively removes the glow effect
}
}
6 changes: 6 additions & 0 deletions packages/deriv_mobile_chart_wrapper/lib/src/enums.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// Specifies which category the indicator belongs to.
enum IndicatorCategory {
momentum,
volatility,
movingAverages,
}
36 changes: 36 additions & 0 deletions packages/deriv_mobile_chart_wrapper/lib/src/helpers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:deriv_chart/deriv_chart.dart';
import 'package:deriv_mobile_chart_wrapper/src/assets.dart';

/// Returns abbreviation name of the indicator for the given [config].
String getIndicatorAbbreviation(IndicatorConfig config) {
// TODO(Ramin): use config.shortTitle after updating to the new version of
// chart package.
switch (config.runtimeType) {
case MACDIndicatorConfig:
return 'MACD';
case RSIIndicatorConfig:
return 'RSI';
case BollingerBandsIndicatorConfig:
return 'BB';
case MAIndicatorConfig:
return 'MA';
default:
return '';
}
}

/// Returns the path to the icon of the indicator for the given [config].
String getIndicatorIconPath(IndicatorConfig config) {
switch (config.runtimeType) {
case MACDIndicatorConfig:
return macdIcon;
case RSIIndicatorConfig:
return rsiIcon;
case BollingerBandsIndicatorConfig:
return bollingerBandsIcon;
case MAIndicatorConfig:
return movingAverageIcon;
default:
return '';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,15 @@ class MobileChartWrapperState extends State<MobileChartWrapper> {
// outside.
showModalBottomSheet(
context: context,
builder: (_) => ChangeNotifierProvider<Repository<IndicatorConfig>>.value(
builder: (_) =>
ChangeNotifierProvider<AddOnsRepository<IndicatorConfig>>.value(
value: indicatorsRepo,
child: ChartBottomSheet(
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.5,
child: const MobileToolsBottomSheetContent(),
child: SafeArea(
child: ChartBottomSheet(
child: SizedBox(
height: MediaQuery.of(context).size.height * 0.5,
child: const MobileToolsBottomSheetContent(),
),
),
),
),
Expand Down
Loading
Loading