diff --git a/.github/workflows/localization.yml b/.github/workflows/localization.yml index 6976e40e1..7107bf766 100644 --- a/.github/workflows/localization.yml +++ b/.github/workflows/localization.yml @@ -25,7 +25,7 @@ jobs: channel: "stable" flutter-version: "3.10.2" cache: true - + - name: Setup Config run: | git config --global user.name 'mobile-apps-deriv' @@ -38,11 +38,11 @@ jobs: ./l10n.sh env: GITHUB_TOKEN: ${{ secrets.PAT }} - - name: Create Pull Request. uses: peter-evans/create-pull-request@76c6f5c20e2111bfee3cd30fae52a25e410f5efc with: token: ${{ secrets.PAT }} title: "refactor(deriv_localizations): Crowdin Localization Generated" + branch: create-pull-request/localisation base: master diff --git a/.github/workflows/version.yaml b/.github/workflows/version.yaml index 7a764da3c..37d18284a 100644 --- a/.github/workflows/version.yaml +++ b/.github/workflows/version.yaml @@ -77,13 +77,6 @@ jobs: run: bash readme.sh working-directory: ./scripts - - name: Create Pull Request on updated changelog and pubspec file. - uses: peter-evans/create-pull-request@76c6f5c20e2111bfee3cd30fae52a25e410f5efc - with: - token: ${{ secrets.PAT }} - title: "chore(version): bump version and update changelog" - base: master - - name: Send Slack Notification uses: ./.github/actions/send_slack_notifications if: ${{ contains(steps.push-tag.outputs.PUSH_OUTPUT, 'Packages updated') }} @@ -91,3 +84,10 @@ jobs: SLACK_WEBHOOK_PACKAGE_UPDATE: ${{ secrets.SLACK_WEBHOOK_PACKAGE_UPDATE }} PR_TITLE: ${{ github.event.pull_request.title }} TAGS: ${{ steps.new-tags.outputs.NEW_TAGS }} + + - name: Create Pull Request on updated changelog and pubspec file. + uses: peter-evans/create-pull-request@76c6f5c20e2111bfee3cd30fae52a25e410f5efc + with: + token: ${{ secrets.PAT }} + title: "chore(version): bump version and update changelog" + base: master diff --git a/CHANGELOG.md b/CHANGELOG.md index 14e05be1d..3df7b664a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,152 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2024-07-17 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.17`](#deriv_auth---v6717) + - [`deriv_passkeys` - `v0.0.3+5`](#deriv_passkeys---v0035) + +--- + +#### `deriv_auth` - `v6.7.17` + + - **REFACTOR**(version): updated the version of flutter deriv api ([#694](https://github.com/regentmarkets/flutter-deriv-packages/issues/694)). ([eac7e8cb](https://github.com/regentmarkets/flutter-deriv-packages/commit/eac7e8cba4e9310d30296e07a47731f08d4d7342)) + +#### `deriv_passkeys` - `v0.0.3+5` + + - **REFACTOR**(version): updated the version of flutter deriv api ([#694](https://github.com/regentmarkets/flutter-deriv-packages/issues/694)). ([eac7e8cb](https://github.com/regentmarkets/flutter-deriv-packages/commit/eac7e8cba4e9310d30296e07a47731f08d4d7342)) + + +## 2024-07-16 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_passkeys` - `v0.0.3+4`](#deriv_passkeys---v0034) + - [`deriv_auth` - `v6.7.16`](#deriv_auth---v6716) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.16` + +--- + +#### `deriv_passkeys` - `v0.0.3+4` + + - **FIX**(deriv_passkeys): fix some missing keys in passkey login page ([#692](https://github.com/regentmarkets/flutter-deriv-packages/issues/692)). ([d944a1c3](https://github.com/regentmarkets/flutter-deriv-packages/commit/d944a1c37f127f35143d9920532f76bc3487ebd4)) + + +## 2024-07-16 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.15`](#deriv_auth---v6715) + - [`deriv_language_selector` - `v0.0.2+9`](#deriv_language_selector---v0029) + - [`deriv_mobile_chart_wrapper` - `v0.0.2+3`](#deriv_mobile_chart_wrapper---v0023) + - [`deriv_passkeys` - `v0.0.3+3`](#deriv_passkeys---v0033) + - [`deriv_ui` - `v0.0.8+1`](#deriv_ui---v0081) + - [`deriv_widgetbook` - `v0.0.2+11`](#deriv_widgetbook---v00211) + - [`update_checker` - `v1.5.1`](#update_checker---v151) + +--- + +#### `deriv_auth` - `v6.7.15` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `deriv_language_selector` - `v0.0.2+9` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `deriv_mobile_chart_wrapper` - `v0.0.2+3` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `deriv_passkeys` - `v0.0.3+3` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `deriv_ui` - `v0.0.8+1` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `deriv_widgetbook` - `v0.0.2+11` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `update_checker` - `v1.5.1` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + + +## 2024-07-15 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.8`](#deriv_ui---v008) + - [`update_checker` - `v1.5.0`](#update_checker---v150) + - [`deriv_auth` - `v6.7.14`](#deriv_auth---v6714) + - [`deriv_mobile_chart_wrapper` - `v0.0.2+2`](#deriv_mobile_chart_wrapper---v0022) + - [`deriv_passkeys` - `v0.0.3+2`](#deriv_passkeys---v0032) + - [`deriv_widgetbook` - `v0.0.2+10`](#deriv_widgetbook---v00210) + - [`deriv_language_selector` - `v0.0.2+8`](#deriv_language_selector---v0028) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.14` + - `deriv_mobile_chart_wrapper` - `v0.0.2+2` + - `deriv_passkeys` - `v0.0.3+2` + - `deriv_widgetbook` - `v0.0.2+10` + - `deriv_language_selector` - `v0.0.2+8` + +--- + +#### `deriv_ui` - `v0.0.8` + + - **FEAT**(deriv_ui): [DERG 2450] Added Timeline Widget to Deriv UI ([#631](https://github.com/regentmarkets/flutter-deriv-packages/issues/631)). ([e34d78b3](https://github.com/regentmarkets/flutter-deriv-packages/commit/e34d78b303358cb5f91abab14a2a042ce3650b0f)) + +#### `update_checker` - `v1.5.0` + + - **FEAT**(deriv_ui): [DERG 2450] Added Timeline Widget to Deriv UI ([#631](https://github.com/regentmarkets/flutter-deriv-packages/issues/631)). ([e34d78b3](https://github.com/regentmarkets/flutter-deriv-packages/commit/e34d78b303358cb5f91abab14a2a042ce3650b0f)) + + ## 2024-07-11 ### Changes diff --git a/README.md b/README.md index b3274b283..829032b11 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,14 @@ cp $HOME/Downloads/commit-msg $HOME/.git/hooks/commit-msg \ ## Using the packages -Each package has been released as git tag with convention as **packageName-vVersionNumber**`(Example: deriv_auth-v6.7.13)`. To use the package, add the following to your pubspec.yaml file: +Each package has been released as git tag with convention as **packageName-vVersionNumber**`(Example: deriv_auth-v6.7.17)`. To use the package, add the following to your pubspec.yaml file: ```yaml deriv_ui: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_ui - ref: deriv_ui-v0.0.7+9 #your prefered version + ref: deriv_ui-v0.0.8+1 #your prefered version ``` ## Packages @@ -30,7 +30,7 @@ deriv_ui: | Name | Description | Version | | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | | [analytics](./packages/analytics) | Used to collect and send analytical information to 'Firebase' and 'Segment'. | [v2.1.0](./packages/analytics/CHANGELOG.md) | -| [deriv_auth](./packages/deriv_auth) | A Dart package that provides Authentication logic for Deriv applications. | [v6.7.13 ](./packages/deriv_auth/CHANGELOG.md) | +| [deriv_auth](./packages/deriv_auth) | A Dart package that provides Authentication logic for Deriv applications. | [v6.7.17 ](./packages/deriv_auth/CHANGELOG.md) | | [deriv_banner](./packages/deriv_banner) | A widget to show banner in apps. | [v0.0.1+1](./packages/deriv_banner/CHANGELOG.md) | | [deriv_bloc_manager](./packages/deriv_bloc_manager) | Provides some tools to manage blocs. | [v0.0.1](./packages/deriv_bloc_manager/CHANGELOG.md) | | [deriv_datadog](./packages/deriv_datadog) | A package that helps you monitor the performance and user interactions of your Flutter app by sending data to Datadog. | [v0.0.1](./packages/deriv_datadog/CHANGELOG.md) | @@ -41,20 +41,20 @@ deriv_ui: | [deriv_http_client](./packages/deriv_http_client) | A package that provides a wrapper for http package. | [v2.0.0](./packages/deriv_http_client/CHANGELOG.md) | | [deriv_lint](./packages/deriv_lint) | A Dart package that provides lint rules for Dart and Flutter. | [v1.0.0](./packages/deriv_lint/CHANGELOG.md) | | [deriv_live_chat](./packages/deriv_live_chat) | A plugin for live chat SDK support to dart. | [v0.0.1+2](./packages/deriv_live_chat/CHANGELOG.md) | -| [deriv_language_selector](./packages/deriv_language_selector) | A package to handle language change of the app. | [v0.0.2+7](./packages/deriv_language_selector/CHANGELOG.md) | +| [deriv_language_selector](./packages/deriv_language_selector) | A package to handle language change of the app. | [v0.0.2+9](./packages/deriv_language_selector/CHANGELOG.md) | | [deriv_localizations](./packages/deriv_localizations) | A Package that contains the localization arb(coming from Crowdin) and dart generated files for flutter_deriv_packages. | [v1.5.1](./packages/deriv_localizations/CHANGELOG.md) | | [deriv_numpad](./packages/deriv_numpad) | Number Pad Widget for number input. | [v1.1.5](./packages/deriv_numpad/CHANGELOG.md) | | [deriv_rudderstack](./packages/deriv_rudderstack) | A plugin that add RudderStack SDK support to Flutter. | [v1.1.0](./packages/deriv_rudderstack/CHANGELOG.md) | | [deriv_store_launcher](./packages/deriv_store_launcher) | A plugin to launch app stores base on platform and manufacturer. | [v0.0.1+1](./packages/deriv_store_launcher/CHANGELOG.md) | | [deriv_technical_analysis](./packages/deriv_technical_analysis) | A Dart package for Technical Analysis. | [v0.0.1](./packages/deriv_technical_analysis/CHANGELOG.md) | | [deriv_theme](./packages/deriv_theme) | A package that contains the theme used by Deriv products. | [v2.4.0](./packages/deriv_theme/CHANGELOG.md) | -| [deriv_ui](./packages/deriv_ui) | A package that contains the UI components used by Deriv products. | [v0.0.7+9](./packages/deriv_ui/CHANGELOG.md) | +| [deriv_ui](./packages/deriv_ui) | A package that contains the UI components used by Deriv products. | [v0.0.8+1](./packages/deriv_ui/CHANGELOG.md) | | [deriv_utilities](./packages/deriv_utilities) | A package that contains the utilities including helper functions, mixins, and extensions. | [v1.0.0](./packages/deriv_utilities/CHANGELOG.md) | | [deriv_websocket](./packages/deriv_web_socket_client) | A package that provides a easy to use websocket client. | [v1.0.1](./packages/deriv_web_socket_client/CHANGELOG.md) | | [deriv_web_view](./packages/deriv_web_view) | Deriv web view package. | [v0.2.2+3](./packages/deriv_web_view/CHANGELOG.md) | -| [deriv_widgetbook](./packages/deriv_widgetbook) | Storybook for Deriv UI Widgets and Components | [v0.0.2+9](./packages/deriv_widgetbook/CHANGELOG.md) | +| [deriv_widgetbook](./packages/deriv_widgetbook) | Storybook for Deriv UI Widgets and Components | [v0.0.2+11](./packages/deriv_widgetbook/CHANGELOG.md) | | [form_builder](./packages/form_builder) | A simpler and cleaner way to create, validate and submit forms. | [v1.0.0+1](./packages/form_builder/CHANGELOG.md) | -| [update_checker](./packages/update_checker) | Check and retrieve update information from the server for the given package. | [v1.4.0](./packages/update_checker/CHANGELOG.md) | +| [update_checker](./packages/update_checker) | Check and retrieve update information from the server for the given package. | [v1.5.1](./packages/update_checker/CHANGELOG.md) | | [deriv_feature_flag](./packages/deriv_feature_flag) | A package to provide feature flag functionality for apps. | [v0.1.1](./packages/deriv_feature_flag/CHANGELOG.md) | ## Environment Setup diff --git a/packages/deriv_auth/CHANGELOG.md b/packages/deriv_auth/CHANGELOG.md index 4e7128bcd..0560ff53b 100644 --- a/packages/deriv_auth/CHANGELOG.md +++ b/packages/deriv_auth/CHANGELOG.md @@ -1,3 +1,19 @@ +## 6.7.17 + + - **REFACTOR**(version): updated the version of flutter deriv api ([#694](https://github.com/regentmarkets/flutter-deriv-packages/issues/694)). ([eac7e8cb](https://github.com/regentmarkets/flutter-deriv-packages/commit/eac7e8cba4e9310d30296e07a47731f08d4d7342)) + +## 6.7.16 + + - Update a dependency to the latest release. + +## 6.7.15 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 6.7.14 + + - Update a dependency to the latest release. + ## 6.7.13 - Update a dependency to the latest release. diff --git a/packages/deriv_auth/example/pubspec.yaml b/packages/deriv_auth/example/pubspec.yaml index a2b58d10e..24fd8a335 100644 --- a/packages/deriv_auth/example/pubspec.yaml +++ b/packages/deriv_auth/example/pubspec.yaml @@ -23,7 +23,7 @@ dependency_overrides: flutter_deriv_api: git: url: git@github.com:deriv-com/flutter-deriv-api.git - ref: v1.0.0 + ref: v1.1.0 dev_dependencies: flutter_test: diff --git a/packages/deriv_auth/pubspec.yaml b/packages/deriv_auth/pubspec.yaml index 8431f7db8..e7a52565d 100644 --- a/packages/deriv_auth/pubspec.yaml +++ b/packages/deriv_auth/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_auth description: Provides deriv authentication functionalities for dart/flutter apps. -version: 6.7.13 +version: 6.7.17 environment: sdk: ">=3.0.0 <4.0.0" @@ -28,7 +28,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_ui - ref: deriv_ui-v0.0.7+9 + ref: deriv_ui-v0.0.8+1 deriv_http_client: git: @@ -39,7 +39,7 @@ dependencies: flutter_deriv_api: git: url: git@github.com:deriv-com/flutter-deriv-api.git - ref: v1.0.0 + ref: v1.1.0 deriv_web_view: git: @@ -57,13 +57,13 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_passkeys - ref: deriv_passkeys-v0.0.3+1 + ref: deriv_passkeys-v0.0.3+5 deriv_language_selector: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_language_selector - ref: deriv_language_selector-v0.0.2+7 + ref: deriv_language_selector-v0.0.2+9 flutter_bloc: ^8.1.3 flutter_svg: ^2.0.7 @@ -79,7 +79,7 @@ dependency_overrides: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_ui - ref: deriv_ui-v0.0.7+9 + ref: deriv_ui-v0.0.8+1 dev_dependencies: mocktail: ^1.0.3 diff --git a/packages/deriv_language_selector/CHANGELOG.md b/packages/deriv_language_selector/CHANGELOG.md index 042e415f2..d26895b3c 100644 --- a/packages/deriv_language_selector/CHANGELOG.md +++ b/packages/deriv_language_selector/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.0.2+9 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 0.0.2+8 + + - Update a dependency to the latest release. + ## 0.0.2+7 - Update a dependency to the latest release. diff --git a/packages/deriv_language_selector/pubspec.yaml b/packages/deriv_language_selector/pubspec.yaml index 65dbe3b48..69b39cafe 100644 --- a/packages/deriv_language_selector/pubspec.yaml +++ b/packages/deriv_language_selector/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_language_selector description: A package to select language for the app. It provides both UI and logic for language selection. -version: 0.0.2+7 +version: 0.0.2+9 publish_to: "none" environment: @@ -14,7 +14,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_ui - ref: deriv_ui-v0.0.7+9 + ref: deriv_ui-v0.0.8+1 shared_preferences: ^2.2.2 flutter_bloc: ^8.1.4 equatable: ^2.0.5 diff --git a/packages/deriv_mobile_chart_wrapper/CHANGELOG.md b/packages/deriv_mobile_chart_wrapper/CHANGELOG.md index 021636ca8..0323f0d7c 100644 --- a/packages/deriv_mobile_chart_wrapper/CHANGELOG.md +++ b/packages/deriv_mobile_chart_wrapper/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.0.2+3 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 0.0.2+2 + + - Update a dependency to the latest release. + ## 0.0.2+1 - **FIX**(deriv_mobile_chart_wrapper): update readme file ([#672](https://github.com/regentmarkets/flutter-deriv-packages/issues/672)). ([13e6b3f3](https://github.com/regentmarkets/flutter-deriv-packages/commit/13e6b3f35ba863098fd9785daaa8ccc7cb23b388)) diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/colours_palettes.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/colours_palettes.dart new file mode 100644 index 000000000..f368f247f --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/colours_palettes.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; + +class ColoursPalettes extends StatefulWidget { + final ValueChanged onColorSelected; + + const ColoursPalettes({super.key, required this.onColorSelected}); + + @override + ColoursPalettesState createState() => ColoursPalettesState(); +} + +class ColoursPalettesState extends State { + final List colors = [ + const Color(0xFFFFFFFF), // White + const Color(0xFFF39230), // Orange + const Color(0xFFEF6B53), // Deep Orange + const Color(0xFFD73737), // Red + const Color(0xFF03BFF0), // Light Blue + const Color(0xFF3271B4), // Blue + const Color(0xFF2FBCB5), // Teal + const Color(0xFF8EC648), // Light Green + const Color(0xFF48A25C), // Green + const Color(0xFFFFF224), // Yellow + const Color(0xFFEE6EA9), // Pink + const Color(0xFF853289), // Purple + ]; + + final Color borderColor = const Color(0xFF85ACB0); + Color? selectedColor; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(20.0), + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 6, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), + itemCount: colors.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + setState(() { + selectedColor = colors[index]; + }); + widget.onColorSelected(colors[index]); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: selectedColor == colors[index] + ? Border.all(color: borderColor, width: 1) + : null, + ), + child: Padding( + padding: const EdgeInsets.all(5.0), + child: Container( + decoration: BoxDecoration( + color: colors[index], + borderRadius: BorderRadius.circular(4), + ), + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/packages/deriv_mobile_chart_wrapper/pubspec.yaml b/packages/deriv_mobile_chart_wrapper/pubspec.yaml index 081c65add..55e0f4d69 100644 --- a/packages/deriv_mobile_chart_wrapper/pubspec.yaml +++ b/packages/deriv_mobile_chart_wrapper/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_mobile_chart_wrapper description: A new Flutter package project. -version: 0.0.2+1 +version: 0.0.2+3 homepage: environment: @@ -31,7 +31,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_ui - ref: deriv_ui-v0.0.7+9 + ref: deriv_ui-v0.0.8+1 provider: ^6.0.5 flutter_svg: ^2.0.9 diff --git a/packages/deriv_passkeys/CHANGELOG.md b/packages/deriv_passkeys/CHANGELOG.md index 1a9902631..18d4afa6a 100644 --- a/packages/deriv_passkeys/CHANGELOG.md +++ b/packages/deriv_passkeys/CHANGELOG.md @@ -1,3 +1,19 @@ +## 0.0.3+5 + + - **REFACTOR**(version): updated the version of flutter deriv api ([#694](https://github.com/regentmarkets/flutter-deriv-packages/issues/694)). ([eac7e8cb](https://github.com/regentmarkets/flutter-deriv-packages/commit/eac7e8cba4e9310d30296e07a47731f08d4d7342)) + +## 0.0.3+4 + + - **FIX**(deriv_passkeys): fix some missing keys in passkey login page ([#692](https://github.com/regentmarkets/flutter-deriv-packages/issues/692)). ([d944a1c3](https://github.com/regentmarkets/flutter-deriv-packages/commit/d944a1c37f127f35143d9920532f76bc3487ebd4)) + +## 0.0.3+3 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 0.0.3+2 + + - Update a dependency to the latest release. + ## 0.0.3+1 - **FIX**(deriv_passkeys): add keys to passkey login page ([#676](https://github.com/regentmarkets/flutter-deriv-packages/issues/676)). ([aa84a46d](https://github.com/regentmarkets/flutter-deriv-packages/commit/aa84a46dfb9cd22a335276c1ae0063ffee7852ef)) diff --git a/packages/deriv_passkeys/lib/src/presentation/pages/effortless_passkeys_login_page.dart b/packages/deriv_passkeys/lib/src/presentation/pages/effortless_passkeys_login_page.dart index 6906c94cc..4e097b3d9 100644 --- a/packages/deriv_passkeys/lib/src/presentation/pages/effortless_passkeys_login_page.dart +++ b/packages/deriv_passkeys/lib/src/presentation/pages/effortless_passkeys_login_page.dart @@ -95,8 +95,6 @@ class EffortlessPasskeysPage extends StatelessWidget child: Padding( padding: const EdgeInsets.all(16), child: TextButton( - key: effortlessPasskeysPageKeys - ?.maybeLaterTextButtonKey, onPressed: () { trackMaybeLater(); onPageClosed(context); @@ -105,6 +103,8 @@ class EffortlessPasskeysPage extends StatelessWidget context.derivPasskeysLocalizations .maybeLater .toUpperCase(), + key: effortlessPasskeysPageKeys + ?.maybeLaterTextButtonKey, style: TextStyle( color: context.theme.colors.coral, ), @@ -237,8 +237,6 @@ class EffortlessPasskeysPage extends StatelessWidget child: Padding( padding: const EdgeInsets.all(16), child: PrimaryButton( - key: effortlessPasskeysPageKeys - ?.createPasskeyButtonKey, onPressed: () { trackCreatePasskey(); context.read().add( @@ -247,6 +245,8 @@ class EffortlessPasskeysPage extends StatelessWidget child: Text( context.derivPasskeysLocalizations .createPasskey, + key: effortlessPasskeysPageKeys + ?.createPasskeyButtonKey, style: TextStyle( color: context.theme.colors.prominent, ), diff --git a/packages/deriv_passkeys/pubspec.yaml b/packages/deriv_passkeys/pubspec.yaml index 1a2a3ccbb..bc67652da 100644 --- a/packages/deriv_passkeys/pubspec.yaml +++ b/packages/deriv_passkeys/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_passkeys description: Deriv Passkeys Flutter Plugin -version: 0.0.3+1 +version: 0.0.3+5 publish_to: "none" environment: @@ -32,7 +32,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_ui - ref: deriv_ui-v0.0.7+9 + ref: deriv_ui-v0.0.8+1 deriv_http_client: git: @@ -43,7 +43,7 @@ dependencies: flutter_deriv_api: git: url: git@github.com:deriv-com/flutter-deriv-api.git - ref: v1.0.0 + ref: v1.1.0 flutter_svg: ^2.0.9 plugin_platform_interface: ^2.0.2 diff --git a/packages/deriv_ui/CHANGELOG.md b/packages/deriv_ui/CHANGELOG.md index 84203b2c2..ae7fb41da 100644 --- a/packages/deriv_ui/CHANGELOG.md +++ b/packages/deriv_ui/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.0.8+1 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 0.0.8 + + - **FEAT**(deriv_ui): [DERG 2450] Added Timeline Widget to Deriv UI ([#631](https://github.com/regentmarkets/flutter-deriv-packages/issues/631)). ([e34d78b3](https://github.com/regentmarkets/flutter-deriv-packages/commit/e34d78b303358cb5f91abab14a2a042ce3650b0f)) + ## 0.0.7+9 - Update a dependency to the latest release. diff --git a/packages/deriv_ui/lib/components/timeline/src/connector_theme.dart b/packages/deriv_ui/lib/components/timeline/src/connector_theme.dart new file mode 100644 index 000000000..2209db96a --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/src/connector_theme.dart @@ -0,0 +1,203 @@ +import 'dart:ui' show lerpDouble; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'connectors.dart'; +import 'timeline_node.dart'; +import 'timeline_theme.dart'; + +/// Defines the visual properties of [SolidLineConnector], connectors inside +/// [TimelineNode]. +/// +/// Descendant widgets obtain the current [ConnectorThemeData] object using +/// `ConnectorTheme.of(context)`. Instances of [ConnectorThemeData] can be +/// customized with [ConnectorThemeData.copyWith]. +/// +/// Typically a [ConnectorThemeData] is specified as part of the overall +/// [TimelineTheme] with [TimelineThemeData.connectorTheme]. +/// +/// All [ConnectorThemeData] properties are `null` by default. When null, the +/// widgets will provide their own defaults. +/// +/// See also: +/// +/// * [TimelineThemeData], which describes the overall theme information for +/// the timeline. +@immutable +class ConnectorThemeData with Diagnosticable { + /// Creates a theme that can be used for [ConnectorTheme] or + /// [TimelineThemeData.connectorTheme]. + const ConnectorThemeData({ + this.color, + this.space, + this.thickness, + this.indent, + }); + + /// The color of [SolidLineConnector]s and connectors inside [TimelineNode]s, + /// and so forth. + final Color? color; + + /// This represents the amount of horizontal or vertical space the connector + /// takes up. + final double? space; + + /// The thickness of the line drawn within the connector. + final double? thickness; + + /// The amount of empty space at the edge of [SolidLineConnector]. + final double? indent; + + /// Creates a copy of this object with the given fields replaced with the new + /// values. + ConnectorThemeData copyWith({ + Color? color, + double? space, + double? thickness, + double? indent, + }) => + ConnectorThemeData( + color: color ?? this.color, + space: space ?? this.space, + thickness: thickness ?? this.thickness, + indent: indent ?? this.indent, + ); + + /// Linearly interpolate between two Connector themes. + /// + /// The argument `t` must not be null. + /// + /// {@macro dart.ui.shadow.lerp} + static ConnectorThemeData lerp( + ConnectorThemeData? a, ConnectorThemeData? b, double t) => + ConnectorThemeData( + color: Color.lerp(a?.color, b?.color, t), + space: lerpDouble(a?.space, b?.space, t), + thickness: lerpDouble(a?.thickness, b?.thickness, t), + indent: lerpDouble(a?.indent, b?.indent, t), + ); + + @override + int get hashCode => Object.hash( + color, + space, + thickness, + indent, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is ConnectorThemeData && + other.color == color && + other.space == space && + other.thickness == thickness && + other.indent == indent; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ColorProperty('color', color, defaultValue: null)) + ..add(DoubleProperty('space', space, defaultValue: null)) + ..add(DoubleProperty('thickness', thickness, defaultValue: null)) + ..add(DoubleProperty('indent', indent, defaultValue: null)); + } +} + +/// An inherited widget that defines the configuration for +/// [SolidLineConnector]s, connectors inside [TimelineNode]s. +class ConnectorTheme extends InheritedTheme { + /// Creates a connector theme that controls the configurations for + /// [SolidLineConnector]s, connectors inside [TimelineNode]s. + const ConnectorTheme({ + required this.data, + required Widget child, + Key? key, + }) : super(key: key, child: child); + + /// The properties for descendant [SolidLineConnector]s, connectors inside + /// [TimelineNode]s. + final ConnectorThemeData data; + + /// The closest instance of this class's [data] value that encloses the given + /// context. + /// + /// If there is no ancestor, it returns [TimelineThemeData.connectorTheme]. + /// Applications can assume that the returned value will not be null. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// ConnectorThemeData theme = ConnectorTheme.of(context); + /// ``` + static ConnectorThemeData of(BuildContext context) { + final ConnectorTheme? connectorTheme = + context.dependOnInheritedWidgetOfExactType(); + return connectorTheme?.data ?? TimelineTheme.of(context).connectorTheme; + } + + @override + Widget wrap(BuildContext context, Widget child) { + final ConnectorTheme? ancestorTheme = + context.findAncestorWidgetOfExactType(); + return identical(this, ancestorTheme) + ? child + : ConnectorTheme(data: data, child: child); + } + + @override + bool updateShouldNotify(ConnectorTheme oldWidget) => data != oldWidget.data; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + data.debugFillProperties(properties); + } +} + +/// Connector component configured through [ConnectorTheme] +mixin ThemedConnectorComponent on Widget { + /// {@template timelines.connector.direction} + /// If this is null, then the [TimelineThemeData.direction] is used. + /// {@endtemplate} + Axis? get direction; + Axis getEffectiveDirection(BuildContext context) => + direction ?? TimelineTheme.of(context).direction; + + /// {@template timelines.connector.thickness} + /// If this is null, then the [ConnectorThemeData.thickness] is used which + /// defaults to 2.0. + /// {@endtemplate} + double? get thickness; + double getEffectiveThickness(BuildContext context) => + thickness ?? ConnectorTheme.of(context).thickness ?? 2.0; + + /// {@template timelines.connector.space} + /// If this is null, then the [ConnectorThemeData.space] is used. If that is + /// also null, then this defaults to double.infinity. + /// {@endtemplate} + double? get space; + double? getEffectiveSpace(BuildContext context) => + space ?? ConnectorTheme.of(context).space; + + double? get indent; + double getEffectiveIndent(BuildContext context) => + indent ?? ConnectorTheme.of(context).indent ?? 0.0; + + double? get endIndent; + double getEffectiveEndIndent(BuildContext context) => + endIndent ?? ConnectorTheme.of(context).indent ?? 0.0; + + Color? get color; + Color getEffectiveColor(BuildContext context) => + color ?? + ConnectorTheme.of(context).color ?? + TimelineTheme.of(context).color; +} diff --git a/packages/deriv_ui/lib/components/timeline/src/connectors.dart b/packages/deriv_ui/lib/components/timeline/src/connectors.dart new file mode 100644 index 000000000..809b7b1c5 --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/src/connectors.dart @@ -0,0 +1,445 @@ +import 'package:flutter/material.dart'; +import 'connector_theme.dart'; +import 'line_painter.dart'; +import 'timelines.dart'; +import 'timeline_node.dart'; +import 'timeline_theme.dart'; + +/// Abstract class for predefined connector widgets. +/// +/// See also: +/// +/// * [SolidLineConnector], which is a [Connector] that draws solid line. +/// * [DashedLineConnector], which is a [Connector] that draws outlined dot. +/// * [TransparentConnector], which is a [Connector] that only takes up space. +abstract class Connector extends StatelessWidget with ThemedConnectorComponent { + /// Creates an connector. + const Connector({ + Key? key, + this.direction, + this.space, + this.thickness, + this.indent, + this.endIndent, + this.color, + }) : assert(thickness == null || thickness >= 0.0), + assert(space == null || space >= 0.0), + assert(indent == null || indent >= 0.0), + assert(endIndent == null || endIndent >= 0.0), + super(key: key); + + /// Creates a solid line connector. + /// + /// See also: + /// + /// * [SolidLineConnector], exactly the same. + factory Connector.solidLine({ + Key? key, + Axis? direction, + double? thickness, + double? space, + double? indent, + double? endIndent, + Color? color, + }) => + SolidLineConnector( + key: key, + direction: direction, + thickness: thickness, + space: space, + indent: indent, + endIndent: endIndent, + color: color, + ); + + /// Creates a dashed line connector. + /// + /// See also: + /// + /// * [DashedLineConnector], exactly the same. + factory Connector.dashedLine({ + Key? key, + Axis? direction, + double? thickness, + double? dash, + double? gap, + double? space, + double? indent, + double? endIndent, + Color? color, + Color? gapColor, + }) => + DashedLineConnector( + key: key, + direction: direction, + thickness: thickness, + dash: dash, + gap: gap, + space: space, + indent: indent, + endIndent: endIndent, + color: color, + gapColor: gapColor, + ); + + /// Creates a dashed transparent connector. + /// + /// See also: + /// + /// * [TransparentConnector], exactly the same. + factory Connector.transparent({ + Key? key, + Axis? direction, + double? indent, + double? endIndent, + double? space, + }) => + TransparentConnector( + key: key, + direction: direction, + indent: indent, + endIndent: endIndent, + space: space, + ); + + /// {@macro timelines.direction} + /// + /// {@macro timelines.connector.direction} + @override + final Axis? direction; + + /// The connector's cross axis size extent. + /// + /// The connector itself is always drawn as a line that is centered within the + /// size specified by this value. + /// {@macro timelines.connector.space} + @override + final double? space; + + /// The thickness of the line drawn within the connector. + /// + /// {@macro timelines.connector.thickness} + @override + final double? thickness; + + /// The amount of empty space to the leading edge of the connector. + /// + /// If this is null, then the [ConnectorThemeData.indent] is used. If that is + /// also null, then this defaults to 0.0. + @override + final double? indent; + + /// The amount of empty space to the trailing edge of the connector. + /// + /// If this is null, then the [ConnectorThemeData.indent] is used. If that is + /// also null, then this defaults to 0.0. + @override + final double? endIndent; + + /// The color to use when painting the line. + /// + /// If this is null, then the [ConnectorThemeData.color] is used. If that is + /// also null, then [TimelineThemeData.color] is used. + @override + final Color? color; +} + +/// A thin line, with padding on either side. +/// +/// The box's total cross axis size(width or height, depend on [direction]) is +/// controlled by [space]. +/// +/// The appropriate padding is automatically computed from the cross axis size. +class SolidLineConnector extends Connector { + /// Creates a solid line connector. + /// + /// The [thickness], [space], [indent], and [endIndent] must be null or + /// non-negative. + const SolidLineConnector({ + Key? key, + Axis? direction, + double? thickness, + double? space, + double? indent, + double? endIndent, + Color? color, + }) : super( + key: key, + direction: direction, + thickness: thickness, + space: space, + indent: indent, + endIndent: endIndent, + color: color, + ); + + @override + Widget build(BuildContext context) { + final Axis direction = getEffectiveDirection(context); + final double thickness = getEffectiveThickness(context); + final Color color = getEffectiveColor(context); + final double? space = getEffectiveSpace(context); + final double indent = getEffectiveIndent(context); + final double endIndent = getEffectiveEndIndent(context); + + switch (direction) { + case Axis.vertical: + return _ConnectorIndent( + direction: direction, + indent: indent, + endIndent: endIndent, + space: space, + child: Container( + width: thickness, + color: color, + ), + ); + case Axis.horizontal: + return _ConnectorIndent( + direction: direction, + indent: indent, + endIndent: endIndent, + space: space, + child: Container( + height: thickness, + color: color, + ), + ); + } + } +} + +/// A decorated thin line, with padding on either side. +/// +/// The box's total cross axis size(width or height, depend on [direction]) is +/// controlled by [space]. +/// +/// The appropriate padding is automatically computed from the cross axis size. +class DecoratedLineConnector extends Connector { + /// Creates a decorated line connector. + /// + /// The [thickness], [space], [indent], and [endIndent] must be null or + /// non-negative. + const DecoratedLineConnector({ + Key? key, + Axis? direction, + double? thickness, + double? space, + double? indent, + double? endIndent, + this.decoration, + }) : super( + key: key, + direction: direction, + thickness: thickness, + space: space, + indent: indent, + endIndent: endIndent, + ); + + /// The decoration to paint line. + /// + /// Use the [SolidLineConnector] class to specify a simple solid color line. + final Decoration? decoration; + + @override + Widget build(BuildContext context) { + final Axis direction = getEffectiveDirection(context); + final double thickness = getEffectiveThickness(context); + final double? space = getEffectiveSpace(context); + final double indent = getEffectiveIndent(context); + final double endIndent = getEffectiveEndIndent(context); + final Color? color = decoration == null ? getEffectiveColor(context) : null; + + switch (direction) { + case Axis.vertical: + return _ConnectorIndent( + direction: direction, + indent: indent, + endIndent: endIndent, + space: space, + child: Container( + width: thickness, + color: color, + decoration: decoration, + ), + ); + case Axis.horizontal: + return _ConnectorIndent( + direction: direction, + indent: indent, + endIndent: endIndent, + space: space, + child: Container( + height: thickness, + color: color, + decoration: decoration, + ), + ); + } + } +} + +/// A thin dashed line, with padding on either side. +/// +/// The box's total cross axis size(width or height, depend on [direction]) is +/// controlled by [space]. +/// +/// The appropriate padding is automatically computed from the cross axis size. +/// +/// See also: +/// +/// * [DashedLinePainter], which is painter that draws this connector. +class DashedLineConnector extends Connector { + /// Creates a dashed line connector. + /// + /// The [thickness], [space], [indent], and [endIndent] must be null or + /// non-negative. + const DashedLineConnector({ + Key? key, + Axis? direction, + double? thickness, + this.dash, + this.gap, + double? space, + double? indent, + double? endIndent, + Color? color, + this.gapColor, + }) : super( + key: key, + direction: direction, + thickness: thickness, + space: space, + indent: indent, + endIndent: endIndent, + color: color, + ); + + /// The dash size of the line drawn within the connector. + /// + /// If this is null, then this defaults to 1.0. + final double? dash; + + /// The gap of the line drawn within the connector. + /// + /// If this is null, then this defaults to 1.0. + final double? gap; + + /// The color to use when painting the gap in the line. + /// + /// If this is null, then the [Colors.transparent] is used. + final Color? gapColor; + + @override + Widget build(BuildContext context) { + final Axis direction = getEffectiveDirection(context); + return _ConnectorIndent( + direction: direction, + indent: getEffectiveIndent(context), + endIndent: getEffectiveEndIndent(context), + space: getEffectiveSpace(context), + child: CustomPaint( + painter: DashedLinePainter( + direction: direction, + color: getEffectiveColor(context), + strokeWidth: getEffectiveThickness(context), + dashSize: dash ?? 1.0, + gapSize: gap ?? 1.0, + gapColor: gapColor ?? Colors.transparent, + ), + child: Container(), + ), + ); + } +} + +/// A transparent connector for start, end [TimelineNode] of the [Timeline]. +/// +/// This connector will be not displayed, it only occupies an area. +class TransparentConnector extends Connector { + /// Creates a transparent connector. + /// + /// The [space], [indent], and [endIndent] must be null or non-negative. + const TransparentConnector({ + Key? key, + Axis? direction, + double? indent, + double? endIndent, + double? space, + }) : super( + key: key, + direction: direction, + indent: indent, + endIndent: endIndent, + space: space, + ); + + @override + Widget build(BuildContext context) => _ConnectorIndent( + direction: getEffectiveDirection(context), + indent: getEffectiveIndent(context), + endIndent: getEffectiveEndIndent(context), + space: getEffectiveSpace(context), + child: Container(), + ); +} + +/// Apply indent to [child]. +class _ConnectorIndent extends StatelessWidget { + /// Creates a indent. + /// + /// The [direction]and [child] must be null. And [space], [indent] and + /// [endIndent] must be null or non-negative. + const _ConnectorIndent({ + required this.direction, + required this.space, + required this.child, + Key? key, + this.indent, + this.endIndent, + }) : assert(space == null || space >= 0), + assert(indent == null || indent >= 0), + assert(endIndent == null || endIndent >= 0), + super(key: key); + + /// {@macro timelines.direction} + final Axis direction; + + /// The connector's cross axis size extent. + /// + /// The connector itself is always drawn as a line that is centered within the + /// size specified by this value. + final double? space; + + /// The amount of empty space to the leading edge of the connector. + final double? indent; + + /// The amount of empty space to the trailing edge of the connector. + final double? endIndent; + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.child} + final Widget child; + + @override + Widget build(BuildContext context) => SizedBox( + width: direction == Axis.vertical ? space : null, + height: direction == Axis.vertical ? null : space, + child: Center( + child: Padding( + padding: direction == Axis.vertical + ? EdgeInsetsDirectional.only( + top: indent ?? 0, + bottom: endIndent ?? 0, + ) + : EdgeInsetsDirectional.only( + start: indent ?? 0, + end: endIndent ?? 0, + ), + child: child, + ), + ), + ); +} diff --git a/packages/deriv_ui/lib/components/timeline/src/indicator_theme.dart b/packages/deriv_ui/lib/components/timeline/src/indicator_theme.dart new file mode 100644 index 000000000..734c1c983 --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/src/indicator_theme.dart @@ -0,0 +1,178 @@ +import 'dart:ui' show lerpDouble; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'indicators.dart'; +import 'timeline_node.dart'; +import 'timeline_theme.dart'; + +/// Defines the visual properties of [DotIndicator], indicators inside +/// [TimelineNode]s. +/// +/// Descendant widgets obtain the current [IndicatorThemeData] object using +/// `IndicatorTheme.of(context)`. Instances of [IndicatorThemeData] can be +/// customized with [IndicatorThemeData.copyWith]. +/// +/// Typically a [IndicatorThemeData] is specified as part of the overall +/// [TimelineTheme] with [TimelineThemeData.indicatorTheme]. +/// +/// All [IndicatorThemeData] properties are `null` by default. When null, the +/// widgets will provide their own defaults. +/// +/// See also: +/// +/// * [TimelineThemeData], which describes the overall theme information for +/// the timeline. +@immutable +class IndicatorThemeData with Diagnosticable { + /// Creates a theme that can be used for [IndicatorTheme] or + /// [TimelineThemeData.indicatorTheme]. + const IndicatorThemeData({ + this.color, + this.size, + this.position, + }); + + /// The color of [DotIndicator]s and indicators inside [TimelineNode]s, and so + /// forth. + final Color? color; + + /// The size of [DotIndicator]s and indicators inside [TimelineNode]s, and so + /// forth in logical pixels. + /// + /// Indicators occupy a square with width and height equal to size. + final double? size; + + /// A position of indicator inside both two connectors. + final double? position; + + /// Creates a copy of this object with the given fields replaced with the new + /// values. + IndicatorThemeData copyWith({ + Color? color, + double? size, + double? position, + }) => + IndicatorThemeData( + color: color ?? this.color, + size: size ?? this.size, + position: position ?? this.position, + ); + + /// Linearly interpolate between two Indicator themes. + /// + /// The argument `t` must not be null. + /// + /// {@macro dart.ui.shadow.lerp} + static IndicatorThemeData lerp( + IndicatorThemeData? a, IndicatorThemeData? b, double t) => + IndicatorThemeData( + color: Color.lerp(a?.color, b?.color, t), + size: lerpDouble(a?.size, b?.size, t), + position: lerpDouble(a?.position, b?.position, t), + ); + + @override + int get hashCode => Object.hash(color, size, position); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is IndicatorThemeData && + other.color == color && + other.size == size && + other.position == position; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ColorProperty('color', color, defaultValue: null)) + ..add(DoubleProperty('size', size, defaultValue: null)) + ..add(DoubleProperty('position', size, defaultValue: null)); + } +} + +/// Controls the default color and size of indicators in a widget subtree. +/// +/// The indicator theme is honored by [TimelineNode], [DotIndicator] and +/// [OutlinedDotIndicator] widgets. +class IndicatorTheme extends InheritedTheme { + /// Creates an indicator theme that controls the color and size for + /// [DotIndicator]s, indicators inside [TimelineNode]s. + const IndicatorTheme({ + required this.data, + required Widget child, + Key? key, + }) : super(key: key, child: child); + + /// The properties for descendant [DotIndicator]s, indicators inside + /// [TimelineNode]s. + final IndicatorThemeData data; + + /// The data from the closest instance of this class that encloses the given + /// context. + /// + /// Defaults to the current [TimelineThemeData.indicatorTheme]. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// IndicatorThemeData theme = IndicatorTheme.of(context); + /// ``` + static IndicatorThemeData of(BuildContext context) { + final IndicatorTheme? indicatorTheme = + context.dependOnInheritedWidgetOfExactType(); + return indicatorTheme?.data ?? TimelineTheme.of(context).indicatorTheme; + } + + @override + Widget wrap(BuildContext context, Widget child) { + final IndicatorTheme? ancestorTheme = + context.findAncestorWidgetOfExactType(); + return identical(this, ancestorTheme) + ? child + : IndicatorTheme(data: data, child: child); + } + + @override + bool updateShouldNotify(IndicatorTheme oldWidget) => data != oldWidget.data; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + data.debugFillProperties(properties); + } +} + +/// Indicator component configured through [IndicatorTheme] +mixin ThemedIndicatorComponent on PositionedIndicator { + /// {@template timelines.indicator.color} + /// Defaults to the current [IndicatorTheme] color, if any. + /// + /// If no [IndicatorTheme] and no [TimelineTheme] is specified, indicators + /// will default to blue. + /// {@endtemplate} + Color? get color; + Color getEffectiveColor(BuildContext context) => + color ?? + IndicatorTheme.of(context).color ?? + TimelineTheme.of(context).color; + + /// {@template timelines.indicator.size} + /// Indicators occupy a square with width and height equal to size. + /// + /// Defaults to the current [IndicatorTheme] size, if any. If there is no + /// [IndicatorTheme], or it does not specify an explicit size, then it + /// defaults to own child size(0.0). + /// {@endtemplate} + double? get size; + double? getEffectiveSize(BuildContext context) => + size ?? IndicatorTheme.of(context).size; +} diff --git a/packages/deriv_ui/lib/components/timeline/src/indicators.dart b/packages/deriv_ui/lib/components/timeline/src/indicators.dart new file mode 100644 index 000000000..f58aa7c7a --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/src/indicators.dart @@ -0,0 +1,262 @@ +import 'package:flutter/material.dart'; +import 'indicator_theme.dart'; +import 'timeline_theme.dart'; + +/// [TimelineNode]'s indicator. +mixin PositionedIndicator on Widget { + /// {@template timelines.indicator.position} + /// If this is null, then the [IndicatorThemeData.position] is used. If that + /// is also null, then this defaults to [TimelineThemeData.indicatorPosition]. + /// {@endtemplate} + double? get position; + double getEffectivePosition(BuildContext context) => + position ?? + IndicatorTheme.of(context).position ?? + TimelineTheme.of(context).indicatorPosition; +} + +/// Abstract class for predefined indicator widgets. +/// +/// See also: +/// +/// * [DotIndicator], which is a [Indicator] that draws dot. +/// * [OutlinedDotIndicator], which is a [Indicator] that draws outlined dot. +/// * [ContainerIndicator], which is a [Indicator] that draws it's child. +abstract class Indicator extends StatelessWidget + with PositionedIndicator, ThemedIndicatorComponent { + /// Creates an indicator. + const Indicator({ + Key? key, + this.size, + this.color, + this.border, + this.position, + this.child, + }) : assert(size == null || size >= 0), + assert(position == null || 0 <= position && position <= 1), + super(key: key); + + /// Creates a dot indicator. + /// + /// See also: + /// + /// * [DotIndicator], exactly the same. + factory Indicator.dot({ + double? size, + Color? color, + double? position, + Border? border, + Widget? child, + }) => + DotIndicator( + size: size, + color: color, + position: position, + border: border, + child: child, + ); + + /// Creates a outlined dot indicator. + /// + /// See also: + /// + /// * [OutlinedDotIndicator], exactly the same. + factory Indicator.outlined({ + double? size, + Color? color, + Color? backgroundColor, + double? position, + double borderWidth = 2.0, + Widget? child, + }) => + OutlinedDotIndicator( + size: size, + color: color, + position: position, + backgroundColor: backgroundColor, + borderWidth: borderWidth, + child: child, + ); + + /// Creates a transparent indicator. + /// + /// See also: + /// + /// * [ContainerIndicator], this is created without child. + factory Indicator.transparent({ + double? size, + double? position, + }) => + ContainerIndicator( + size: size, + position: position, + ); + + /// Creates a widget indicator. + /// + /// See also: + /// + /// * [OutlinedDotIndicator], exactly the same. + factory Indicator.widget({ + double? size, + double? position, + Widget? child, + }) => + ContainerIndicator( + size: size, + position: position, + child: child, + ); + + /// The size of the dot in logical pixels. + /// + /// {@macro timelines.indicator.size} + @override + final double? size; + + /// The color to use when drawing the dot. + /// + /// {@macro timelines.indicator.color} + @override + final Color? color; + + /// The position of a indicator between the two connectors. + /// + /// {@macro timelines.indicator.position} + @override + final double? position; + + /// The border to use when drawing the dot's outline. + final BoxBorder? border; + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.child} + final Widget? child; +} + +/// A widget that displays an [child]. The [child] if null, the indicator is not +/// visible. +class ContainerIndicator extends Indicator { + /// Creates a container indicator. + const ContainerIndicator({ + Key? key, + double? size, + double? position, + this.child, + }) : super( + key: key, + size: size, + position: position, + color: Colors.transparent, + ); + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.child} + final Widget? child; + + @override + Widget build(BuildContext context) { + final double? size = getEffectiveSize(context); + return Container( + width: size, + height: size, + child: child, + ); + } +} + +/// A widget that displays an dot. +class DotIndicator extends Indicator { + /// Creates a dot indicator. + /// + /// The [size] must be null or non-negative. + const DotIndicator({ + Key? key, + double? size, + Color? color, + double? position, + this.border, + this.child, + }) : super( + key: key, + size: size, + color: color, + position: position, + ); + + /// The border to use when drawing the dot's outline. + final BoxBorder? border; + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.child} + final Widget? child; + + @override + Widget build(BuildContext context) { + final double? effectiveSize = getEffectiveSize(context); + final Color effectiveColor = getEffectiveColor(context); + return Center( + child: Container( + width: effectiveSize ?? ((child == null) ? 15.0 : null), + height: effectiveSize ?? ((child == null) ? 15.0 : null), + child: child, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: effectiveColor, + border: border, + ), + ), + ); + } +} + +/// A widget that displays an outlined dot. +class OutlinedDotIndicator extends Indicator { + /// Creates a outlined dot indicator. + /// + /// The [size] must be null or non-negative. + const OutlinedDotIndicator({ + Key? key, + double? size, + Color? color, + double? position, + this.backgroundColor, + this.borderWidth = 2.0, + this.child, + }) : assert(size == null || size >= 0), + assert(position == null || 0 <= position && position <= 1), + super( + key: key, + size: size, + color: color, + position: position, + ); + + /// The color to use when drawing the dot in outline. + /// + /// {@macro timelines.indicator.color} + final Color? backgroundColor; + + /// The width of this outline, in logical pixels. + final double borderWidth; + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.child} + final Widget? child; + + @override + Widget build(BuildContext context) => DotIndicator( + size: size, + color: backgroundColor ?? Colors.transparent, + position: position, + border: Border.all( + color: color ?? getEffectiveColor(context), + width: borderWidth, + ), + child: child, + ); +} diff --git a/packages/deriv_ui/lib/components/timeline/src/line_painter.dart b/packages/deriv_ui/lib/components/timeline/src/line_painter.dart new file mode 100644 index 000000000..7b7a2168e --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/src/line_painter.dart @@ -0,0 +1,206 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'connectors.dart'; + +/// Paints a [DashedLineConnector]. +/// +/// Draw the line like this: +/// ``` +/// 0 > [dash][gap][dash][gap] < constraints size +/// ``` +/// +/// [dashSize] specifies the size of [dash]. and [gapSize] specifies the size of +/// [gap]. +/// +/// When using the default colors, this painter draws a dotted line or dashed +/// line that familiar. +/// If set other [gapColor], this painter draws a line that alternates between +/// two colors. +class DashedLinePainter extends CustomPainter { + /// Creates a dashed line painter. + /// + /// The [dashSize] argument must be 1 or more, and the [gapSize] and + /// [strokeWidth] arguments must be positive numbers. + /// + /// The [direction], [color], [gapColor] and [strokeCap] arguments must not be + /// null. + const DashedLinePainter({ + required this.direction, + required this.color, + this.gapColor = Colors.transparent, + this.dashSize = 1.0, + this.gapSize = 1.0, + this.strokeWidth = 1.0, + this.strokeCap = StrokeCap.square, + }) : assert(dashSize >= 1), + assert(gapSize >= 0), + assert(strokeWidth >= 0); + + /// {@macro timelines.direction} + final Axis direction; + + /// The color to paint dash of line. + final Color color; + + /// The color to paint gap(another dash) of line. + final Color gapColor; + + /// The size of dash + final double dashSize; + + /// The size of gap, it also draws [gapColor] + final double gapSize; + + /// The stroke width of dash and gap. + final double strokeWidth; + + /// Styles to use for line endings. + final StrokeCap strokeCap; + + @override + void paint(Canvas canvas, Size size) { + final Paint paint = Paint() + ..strokeWidth = strokeWidth + ..strokeCap = strokeCap + ..style = PaintingStyle.stroke; + + _DashOffset offset = _DashOffset( + containerSize: size, + strokeWidth: strokeWidth, + dashSize: dashSize, + gapSize: gapSize, + axis: direction, + ); + + while (offset.hasNext) { + // draw dash + paint.color = color; + canvas.drawLine( + offset, + offset.translateDashSize(), + paint, + ); + offset = offset.translateDashSize(); + + // draw gap + if (gapColor != Colors.transparent) { + paint.color = gapColor; + canvas.drawLine( + offset, + offset.translateGapSize(), + paint, + ); + } + offset = offset.translateGapSize(); + } + } + + @override + bool shouldRepaint(DashedLinePainter oldDelegate) => + direction != oldDelegate.direction || + color != oldDelegate.color || + gapColor != oldDelegate.gapColor || + dashSize != oldDelegate.dashSize || + gapSize != oldDelegate.gapSize || + strokeWidth != oldDelegate.strokeWidth || + strokeCap != oldDelegate.strokeCap; +} + +class _DashOffset extends Offset { + factory _DashOffset({ + required Size containerSize, + required double strokeWidth, + required double dashSize, + required double gapSize, + required Axis axis, + }) => + _DashOffset._( + dx: axis == Axis.vertical ? containerSize.width / 2 : 0, + dy: axis == Axis.vertical ? 0 : containerSize.height / 2, + strokeWidth: strokeWidth, + containerSize: containerSize, + dashSize: dashSize, + gapSize: gapSize, + axis: axis, + ); + + const _DashOffset._({ + required double dx, + required double dy, + required this.strokeWidth, + required this.containerSize, + required this.dashSize, + required this.gapSize, + required this.axis, + }) : super(dx, dy); + + final Size containerSize; + final double strokeWidth; + final double dashSize; + final double gapSize; + final Axis axis; + + double get offset { + if (axis == Axis.vertical) { + return dy; + } else { + return dx; + } + } + + bool get hasNext { + if (axis == Axis.vertical) { + return offset < containerSize.height; + } else { + return offset < containerSize.width; + } + } + + _DashOffset translateDashSize() => _translateDirectionally(dashSize); + + _DashOffset translateGapSize() => + _translateDirectionally(gapSize + strokeWidth); + + _DashOffset _translateDirectionally(double offset) { + if (axis == Axis.vertical) { + return translate(0, offset) as _DashOffset; + } else { + return translate(offset, 0) as _DashOffset; + } + } + + @override + Offset translate(double translateX, double translateY) { + double dx, dy; + if (axis == Axis.vertical) { + dx = this.dx; + dy = this.dy + translateY; + } else { + dx = this.dx + translateX; + dy = this.dy; + } + return copyWith( + dx: min(dx, containerSize.width), + dy: min(dy, containerSize.height), + ); + } + + _DashOffset copyWith({ + double? dx, + double? dy, + Size? containerSize, + double? strokeWidth, + double? dashSize, + double? gapSize, + Axis? axis, + }) => + _DashOffset._( + dx: dx ?? this.dx, + dy: dy ?? this.dy, + containerSize: containerSize ?? this.containerSize, + strokeWidth: strokeWidth ?? this.strokeWidth, + dashSize: dashSize ?? this.dashSize, + gapSize: gapSize ?? this.gapSize, + axis: axis ?? this.axis, + ); +} diff --git a/packages/deriv_ui/lib/components/timeline/src/timeline_node.dart b/packages/deriv_ui/lib/components/timeline/src/timeline_node.dart new file mode 100644 index 000000000..c1715f73d --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/src/timeline_node.dart @@ -0,0 +1,216 @@ +import 'package:flutter/material.dart'; +import 'connectors.dart'; +import 'indicators.dart'; +import 'timeline_theme.dart'; +import 'util.dart'; + +/// [TimelineTile]'s timeline node +mixin TimelineTileNode on Widget { + /// {@template timelines.node.position} + /// If this is null, then the [TimelineThemeData.nodePosition] is used. + /// {@endtemplate} + double? get position; + double getEffectivePosition(BuildContext context) => + position ?? TimelineTheme.of(context).nodePosition; +} + +/// A widget that displays indicator and two connectors. +/// +/// The [indicator] displayed between the [startConnector] and [endConnector] +class TimelineNode extends StatelessWidget with TimelineTileNode { + /// Creates a timeline node. + /// + /// The [indicatorPosition] must be null or a value between 0 and 1. + const TimelineNode({ + Key? key, + this.direction, + this.startConnector, + this.endConnector, + this.indicator = const ContainerIndicator(), + this.indicatorPosition, + this.position, + this.overlap, + }) : assert(indicatorPosition == null || + 0 <= indicatorPosition && indicatorPosition <= 1), + super(key: key); + + /// Creates a timeline node that connects the dot indicator in a solid line. + TimelineNode.simple({ + Key? key, + Axis? direction, + Color? color, + double? lineThickness, + double? nodePosition, + double? indicatorPosition, + double? indicatorSize, + Widget? indicatorChild, + double? indent, + double? endIndent, + bool drawStartConnector = true, + bool drawEndConnector = true, + bool? overlap, + }) : this( + key: key, + direction: direction, + startConnector: drawStartConnector + ? SolidLineConnector( + direction: direction, + color: color, + thickness: lineThickness, + indent: indent, + endIndent: endIndent, + ) + : null, + endConnector: drawEndConnector + ? SolidLineConnector( + direction: direction, + color: color, + thickness: lineThickness, + indent: indent, + endIndent: endIndent, + ) + : null, + indicator: DotIndicator( + child: indicatorChild, + position: indicatorPosition, + size: indicatorSize, + color: color, + ), + indicatorPosition: indicatorPosition, + position: nodePosition, + overlap: overlap, + ); + + /// {@macro timelines.direction} + final Axis? direction; + + /// The connector of the start edge of this node + final Widget? startConnector; + + /// The connector of the end edge of this node + final Widget? endConnector; + + /// The indicator of the node + final Widget indicator; + + /// The position of a indicator between the two connectors. + /// + /// {@macro timelines.indicator.position} + final double? indicatorPosition; + + /// A position of timeline node between both two contents. + /// + /// {@macro timelines.node.position} + @override + final double? position; + + /// Determine whether each connectors and indicator will overlap. + /// + /// When each connectors overlap, they are drawn from the center offset of the + /// indicator. + final bool? overlap; + + double _getEffectiveIndicatorPosition(BuildContext context) { + double? indicatorPosition = this.indicatorPosition; + return indicatorPosition ??= (indicator is PositionedIndicator) + ? (indicator as PositionedIndicator).getEffectivePosition(context) + : TimelineTheme.of(context).indicatorPosition; + } + + bool _getEffectiveOverlap(BuildContext context) { + final bool overlap = + this.overlap ?? TimelineTheme.of(context).nodeItemOverlap; + return overlap; + } + + @override + Widget build(BuildContext context) { + final Axis direction = + this.direction ?? TimelineTheme.of(context).direction; + final bool overlap = _getEffectiveOverlap(context); + //TODO: support both flex and logical pixel + final double indicatorFlex = _getEffectiveIndicatorPosition(context); + Widget line = indicator; + final List lineItems = [ + if (indicatorFlex > 0) + Flexible( + flex: (indicatorFlex * kFlexMultiplier).toInt(), + child: startConnector ?? const TransparentConnector(), + ), + if (!overlap) indicator, + if (indicatorFlex < 1) + Flexible( + flex: ((1 - indicatorFlex) * kFlexMultiplier).toInt(), + child: endConnector ?? const TransparentConnector(), + ), + ]; + + switch (direction) { + case Axis.vertical: + line = Column( + mainAxisSize: MainAxisSize.min, + children: lineItems, + ); + break; + case Axis.horizontal: + line = Row( + mainAxisSize: MainAxisSize.min, + children: lineItems, + ); + break; + } + + Widget result; + if (overlap) { + Widget positionedIndicator = indicator; + final List positionedIndicatorItems = [ + if (indicatorFlex > 0) + Flexible( + flex: (indicatorFlex * kFlexMultiplier).toInt(), + child: const TransparentConnector(), + ), + indicator, + Flexible( + flex: ((1 - indicatorFlex) * kFlexMultiplier).toInt(), + child: const TransparentConnector(), + ), + ]; + + switch (direction) { + case Axis.vertical: + positionedIndicator = Column( + mainAxisSize: MainAxisSize.min, + children: positionedIndicatorItems, + ); + break; + case Axis.horizontal: + positionedIndicator = Row( + mainAxisSize: MainAxisSize.min, + children: positionedIndicatorItems, + ); + break; + } + + result = Stack( + alignment: Alignment.center, + children: [ + line, + positionedIndicator, + ], + ); + } else { + result = line; + } + + if (TimelineTheme.of(context).direction != direction) { + result = TimelineTheme( + data: TimelineTheme.of(context).copyWith( + direction: direction, + ), + child: result, + ); + } + + return result; + } +} diff --git a/packages/deriv_ui/lib/components/timeline/src/timeline_theme.dart b/packages/deriv_ui/lib/components/timeline/src/timeline_theme.dart new file mode 100644 index 000000000..a1c116fd6 --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/src/timeline_theme.dart @@ -0,0 +1,373 @@ +import 'dart:ui'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'connector_theme.dart'; +import 'indicator_theme.dart'; +import 'timelines.dart'; + +/// Applies a theme to descendant timeline widgets. +/// +/// A theme describes the colors and typographic choices of an application. +/// +/// Descendant widgets obtain the current theme's [TimelineThemeData] object +/// using [TimelineTheme.of]. When a widget uses [TimelineTheme.of], it is +/// automatically rebuilt if the theme later changes, so that the changes can be +/// applied. +/// +/// See also: +/// +/// * [TimelineThemeData], which describes the actual configuration of a theme. +class TimelineTheme extends StatelessWidget { + /// Applies the given theme [data] to [child]. + /// + /// The [data] and [child] arguments must not be null. + const TimelineTheme({ + required this.data, + required this.child, + Key? key, + }) : super(key: key); + + /// Specifies the direction for descendant widgets. + final TimelineThemeData data; + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.child} + final Widget child; + + static final TimelineThemeData _kFallbackTheme = TimelineThemeData.fallback(); + + /// The data from the closest [TimelineTheme] instance that encloses the given + /// context. + /// + /// Defaults to [ThemeData.fallback] if there is no [Theme] in the given + /// build context. + /// + /// When the [TimelineTheme] is actually created in the same `build` function + /// (possibly indirectly, e.g. as part of a [Timeline]), the `context` + /// argument to the `build` function can't be used to find the [TimelineTheme] + /// (since it's "above" the widget being returned). In such cases, the + /// following technique with a [Builder] can be used to provide a new scope + /// with a [BuildContext] that is "under" the [TimelineTheme]: + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// // TODO: replace to Timeline + /// return TimelineTheme( + /// data: TimelineThemeData.vertical(), + /// child: Builder( + /// // Create an inner BuildContext so that we can refer to the Theme with TimelineTheme.of(). + /// builder: (BuildContext context) { + /// return Center( + /// child: TimelineNode( + /// direction: TimelineTheme.of(context).direction, + /// child: Text('Example'), + /// ), + /// ); + /// }, + /// ), + /// ); + /// } + /// ``` + static TimelineThemeData of(BuildContext context) { + final _InheritedTheme? inheritedTheme = + context.dependOnInheritedWidgetOfExactType<_InheritedTheme>(); + return inheritedTheme?.theme.data ?? _kFallbackTheme; + } + + @override + Widget build(BuildContext context) => _InheritedTheme( + theme: this, + child: IndicatorTheme( + data: data.indicatorTheme, + child: child, + ), + ); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add( + DiagnosticsProperty('data', data, showName: false)); + } +} + +class _InheritedTheme extends InheritedTheme { + const _InheritedTheme({ + required this.theme, + required Widget child, + Key? key, + }) : super(key: key, child: child); + + final TimelineTheme theme; + + @override + Widget wrap(BuildContext context, Widget child) { + final _InheritedTheme? ancestorTheme = + context.findAncestorWidgetOfExactType<_InheritedTheme>(); + return identical(this, ancestorTheme) + ? child + : TimelineTheme(data: theme.data, child: child); + } + + @override + bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data; +} + +/// Defines the configuration of the overall visual [TimelineTheme] for a +/// [Timeline] or a widget subtree within the app. +/// +/// The [Timeline] theme property can be used to configure the appearance of the +/// entire timeline. Widget subtree's within an timeline can override the +/// timeline's theme by including a [TimelineTheme] widget at the top of the +/// subtree. +/// +/// Widgets whose appearance should align with the overall theme can obtain the +/// current theme's configuration with [TimelineTheme.of]. +/// +/// The static [TimelineTheme.of] method finds the [TimelineThemeData] value +/// specified for the nearest [BuildContext] ancestor. This lookup is +/// inexpensive, essentially just a single HashMap access. It can sometimes be a +/// little confusing because [TimelineTheme.of] can not see a [TimelineTheme] +/// widget that is defined in the current build method's context. To overcome +/// that, create a new custom widget for the subtree that appears below the new +/// [TimelineTheme], or insert a widget that creates a new BuildContext, like +/// [Builder]. +/// +/// {@tool snippet} +/// In this example, the [Container] widget uses [Theme.of] to retrieve the +/// color from the theme's [color] to draw an amber square. +/// The [Builder] widget separates the parent theme's [BuildContext] from the +/// child's [BuildContext]. +/// +/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/theme_data.png) +/// +/// ```dart +/// TimelineTheme( +/// data: TimelineThemeData( +/// color: Colors.red, +/// ), +/// child: Builder( +/// builder: (BuildContext context) { +/// return Container( +/// width: 100, +/// height: 100, +/// color: TimelineTheme.of(context).color, +/// ); +/// }, +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// {@tool snippet} +@immutable +class TimelineThemeData with Diagnosticable { + /// Create a [TimelineThemeData] that's used to configure a [TimelineTheme]. + /// + /// See also: + /// + /// * [TimelineThemeData.vertical], which creates a vertical direction + /// TimelineThemeData. + /// * [TimelineThemeData.horizontal], which creates a horizontal direction + /// TimelineThemeData. + factory TimelineThemeData({ + Axis? direction, + Color? color, + double? nodePosition, + bool? nodeItemOverlap, + double? indicatorPosition, + IndicatorThemeData? indicatorTheme, + ConnectorThemeData? connectorTheme, + }) { + direction ??= Axis.vertical; + color ??= Colors + .blue; // TODO: Need to change the default color to the theme color? + nodePosition ??= 0.5; + nodeItemOverlap ??= false; + indicatorPosition ??= 0.5; + indicatorTheme ??= const IndicatorThemeData(); + connectorTheme ??= const ConnectorThemeData(); + return TimelineThemeData.raw( + direction: direction, + color: color, + nodePosition: nodePosition, + nodeItemOverlap: nodeItemOverlap, + indicatorPosition: indicatorPosition, + indicatorTheme: indicatorTheme, + connectorTheme: connectorTheme, + ); + } + + /// The default direction theme. Same as [TimelineThemeData.vertical]. + /// + /// This is used by [TimelineTheme.of] when no theme has been specified. + factory TimelineThemeData.fallback() => TimelineThemeData.vertical(); + + /// Create a [TimelineThemeData] given a set of exact values. All the values + /// must be specified. They all must also be non-null. + /// + /// This will rarely be used directly. It is used by [lerp] to create + /// intermediate themes based on two themes created with the + /// [TimelineThemeData] constructor. + const TimelineThemeData.raw({ + required this.direction, + required this.color, + required this.nodePosition, + required this.nodeItemOverlap, + required this.indicatorPosition, + required this.indicatorTheme, + required this.connectorTheme, + }); + + /// A default vertical theme. + factory TimelineThemeData.vertical() => TimelineThemeData( + direction: Axis.vertical, + ); + + /// A default horizontal theme. + factory TimelineThemeData.horizontal() => TimelineThemeData( + direction: Axis.horizontal, + ); + + /// {@macro timelines.direction} + final Axis direction; + + /// The color for major parts of the timeline (indicator, connector, etc) + final Color color; + + /// The position for [TimelineNode] in [TimelineTile]. + /// + /// Defaults to 0.5. + final double nodePosition; + + /// Determine whether each connectors and indicator will overlap in + /// [TimelineNode]. + /// + /// When each connectors overlap, they are drawn from the center offset of the + /// indicator. + final bool nodeItemOverlap; + + /// The position for indicator in [TimelineNode]. + /// + /// Defaults to 0.5. + final double indicatorPosition; + + /// A theme for customizing the appearance and layout of + /// [ThemedIndicatorComponent] widgets. + final IndicatorThemeData indicatorTheme; + + /// A theme for customizing the appearance and layout of + /// [ThemedConnectorComponent] widgets. + final ConnectorThemeData connectorTheme; + + /// Creates a copy of this theme but with the given fields replaced with the + /// new values. + TimelineThemeData copyWith({ + Axis? direction, + Color? color, + double? nodePosition, + bool? nodeItemOverlap, + double? indicatorPosition, + IndicatorThemeData? indicatorTheme, + ConnectorThemeData? connectorTheme, + }) => + TimelineThemeData.raw( + direction: direction ?? this.direction, + color: color ?? this.color, + nodePosition: nodePosition ?? this.nodePosition, + nodeItemOverlap: nodeItemOverlap ?? this.nodeItemOverlap, + indicatorPosition: indicatorPosition ?? this.indicatorPosition, + indicatorTheme: indicatorTheme ?? this.indicatorTheme, + connectorTheme: connectorTheme ?? this.connectorTheme, + ); + + /// Linearly interpolate between two themes. + /// + /// The arguments must not be null. + /// + /// {@macro dart.ui.shadow.lerp} + static TimelineThemeData lerp( + TimelineThemeData a, TimelineThemeData b, double t) => + // Warning: make sure these properties are in the exact same order as in + // hashValues() and in the raw constructor and in the order of fields in + // the class and in the lerp() method. + TimelineThemeData.raw( + direction: t < 0.5 ? a.direction : b.direction, + color: Color.lerp(a.color, b.color, t)!, + nodePosition: lerpDouble(a.nodePosition, b.nodePosition, t)!, + nodeItemOverlap: t < 0.5 ? a.nodeItemOverlap : b.nodeItemOverlap, + indicatorPosition: + lerpDouble(a.indicatorPosition, b.indicatorPosition, t)!, + indicatorTheme: + IndicatorThemeData.lerp(a.indicatorTheme, b.indicatorTheme, t), + connectorTheme: + ConnectorThemeData.lerp(a.connectorTheme, b.connectorTheme, t), + ); + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + // Warning: make sure these properties are in the exact same order as in + // hashValues() and in the raw constructor and in the order of fields in + // the class and in the lerp() method. + return other is TimelineThemeData && + other.direction == direction && + other.color == color && + other.nodePosition == nodePosition && + other.nodeItemOverlap == nodeItemOverlap && + other.indicatorPosition == indicatorPosition && + other.indicatorTheme == indicatorTheme && + other.connectorTheme == connectorTheme; + } + + @override + int get hashCode { + // Warning: For the sanity of the reader, please make sure these properties + // are in the exact same order as in operator == and in the raw constructor + // and in the order of fields in the class and in the lerp() method. + final List values = [ + direction, + color, + nodePosition, + nodeItemOverlap, + indicatorPosition, + indicatorTheme, + connectorTheme, + ]; + return Object.hashAll(values); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + final TimelineThemeData defaultData = TimelineThemeData.fallback(); + properties + ..add(DiagnosticsProperty('direction', direction, + defaultValue: defaultData.direction, level: DiagnosticLevel.debug)) + ..add(ColorProperty('color', color, + defaultValue: defaultData.color, level: DiagnosticLevel.debug)) + ..add(DoubleProperty('nodePosition', nodePosition, + defaultValue: defaultData.nodePosition, level: DiagnosticLevel.debug)) + ..add(FlagProperty('nodeItemOverlap', + value: nodeItemOverlap, ifTrue: 'overlap connector and indicator')) + ..add(DoubleProperty('indicatorPosition', indicatorPosition, + defaultValue: defaultData.indicatorPosition, + level: DiagnosticLevel.debug)) + ..add(DiagnosticsProperty( + 'indicatorTheme', + indicatorTheme, + defaultValue: defaultData.indicatorTheme, + level: DiagnosticLevel.debug, + )) + ..add(DiagnosticsProperty( + 'connectorTheme', + connectorTheme, + defaultValue: defaultData.connectorTheme, + level: DiagnosticLevel.debug, + )); + } +} diff --git a/packages/deriv_ui/lib/components/timeline/src/timeline_tile.dart b/packages/deriv_ui/lib/components/timeline/src/timeline_tile.dart new file mode 100644 index 000000000..9884f80b3 --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/src/timeline_tile.dart @@ -0,0 +1,215 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'indicator_theme.dart'; +import 'timeline_node.dart'; +import 'timeline_theme.dart'; +import 'util.dart'; + +/// Align the timeline node within the timeline tile. +enum TimelineNodeAlign { + /// Align [TimelineTile.node] to start side. + start, + + /// Align [TimelineTile.node] to end side. + end, + + /// Align according to the [TimelineTile.nodePosition]. + basic, +} + +/// A widget that displays timeline node and two contents. +/// +/// The [contents] are displayed on the end side, and the [oppositeContents] are +/// displayed on the start side. +/// The [node] is displayed between the two. +class TimelineTile extends StatelessWidget { + const TimelineTile({ + required this.node, + Key? key, + this.direction, + this.nodeAlign = TimelineNodeAlign.basic, + this.nodePosition, + this.contents, + this.oppositeContents, + this.mainAxisExtent, + this.crossAxisExtent, + }) : assert( + nodeAlign == TimelineNodeAlign.basic || + (nodeAlign != TimelineNodeAlign.basic && nodePosition == null), + 'Cannot provide both a nodeAlign and a nodePosition', + ), + assert(nodePosition == null || nodePosition >= 0), + super(key: key); + + /// {@template timelines.direction} + /// The axis along which the timeline scrolls. + /// {@endtemplate} + final Axis? direction; + + /// A widget that displays indicator and two connectors. + final Widget node; + + /// Align the [node] within the timeline tile. + /// + /// If try to use indicators with different sizes in each timeline tile, the + /// timeline node may be broken. + /// This can be prevented by set [IndicatorThemeData.size] to an appropriate + /// size. + /// + /// If [nodeAlign] is not [TimelineNodeAlign.basic], then [nodePosition] is + /// ignored. + final TimelineNodeAlign nodeAlign; + + /// A position of [node] inside both two contents. + /// + /// {@macro timelines.node.position} + final double? nodePosition; + + /// The contents to display inside the timeline tile. + final Widget? contents; + + /// The contents to display on the opposite side of the [contents]. + final Widget? oppositeContents; + + /// The extent of the child in the scrolling axis. + /// If the scroll axis is vertical, this extent is the child's height. If the + /// scroll axis is horizontal, this extent is the child's width. + /// + /// If non-null, forces the tile to have the given extent in the scroll + /// direction. + /// + /// Specifying an [mainAxisExtent] is more efficient than letting the tile + /// determine their own extent because the because it don't use the Intrinsic + /// widget([IntrinsicHeight]/[IntrinsicWidth]) when building. + final double? mainAxisExtent; + + /// The extent of the child in the non-scrolling axis. + /// + /// If the scroll axis is vertical, this extent is the child's width. If the + /// scroll axis is horizontal, this extent is the child's height. + final double? crossAxisExtent; + + double _getEffectiveNodePosition(BuildContext context) { + if (nodeAlign == TimelineNodeAlign.start) { + return 0; + } + if (nodeAlign == TimelineNodeAlign.end) { + return 1; + } + double? nodePosition = this.nodePosition; + return nodePosition ??= (node is TimelineTileNode) + ? (node as TimelineTileNode).getEffectivePosition(context) + : TimelineTheme.of(context).nodePosition; + } + + @override + Widget build(BuildContext context) { + // TODO: reduce direction check + final Axis direction = + this.direction ?? TimelineTheme.of(context).direction; + final double nodeFlex = + _getEffectiveNodePosition(context) * kFlexMultiplier; + + final double minNodeExtent = + TimelineTheme.of(context).indicatorTheme.size ?? 0.0; + final List items = [ + if (nodeFlex > 0) + Expanded( + flex: nodeFlex.toInt(), + child: Align( + alignment: direction == Axis.vertical + ? AlignmentDirectional.centerEnd + : Alignment.bottomCenter, + child: oppositeContents ?? const SizedBox.shrink(), + ), + ), + ConstrainedBox( + constraints: BoxConstraints( + minWidth: direction == Axis.vertical ? minNodeExtent : 0.0, + minHeight: direction == Axis.vertical ? 0.0 : minNodeExtent, + ), + child: node, + ), + if (nodeFlex < kFlexMultiplier) + Expanded( + flex: (kFlexMultiplier - nodeFlex).toInt(), + child: Align( + alignment: direction == Axis.vertical + ? AlignmentDirectional.centerStart + : Alignment.topCenter, + child: contents ?? const SizedBox.shrink(), + ), + ), + ]; + + Widget result; + switch (direction) { + case Axis.vertical: + result = Row( + mainAxisAlignment: MainAxisAlignment.center, + children: items, + ); + + if (mainAxisExtent != null) { + result = SizedBox( + width: crossAxisExtent, + height: mainAxisExtent, + child: result, + ); + } else { + result = IntrinsicHeight( + child: result, + ); + + if (crossAxisExtent != null) { + result = SizedBox( + width: crossAxisExtent, + child: result, + ); + } + } + break; + case Axis.horizontal: + result = Column( + mainAxisAlignment: MainAxisAlignment.center, + children: items, + ); + if (mainAxisExtent != null) { + result = SizedBox( + width: mainAxisExtent, + height: crossAxisExtent, + child: result, + ); + } else { + result = IntrinsicWidth( + child: result, + ); + + if (crossAxisExtent != null) { + result = SizedBox( + height: crossAxisExtent, + child: result, + ); + } + } + break; + default: + throw ArgumentError.value(direction, '$direction is invalid.'); + } + + result = Align( + child: result, + ); + + if (TimelineTheme.of(context).direction != direction) { + result = TimelineTheme( + data: TimelineTheme.of(context).copyWith( + direction: direction, + ), + child: result, + ); + } + + return result; + } +} diff --git a/packages/deriv_ui/lib/components/timeline/src/timeline_tile_builder.dart b/packages/deriv_ui/lib/components/timeline/src/timeline_tile_builder.dart new file mode 100644 index 000000000..d357e7e13 --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/src/timeline_tile_builder.dart @@ -0,0 +1,545 @@ +import 'package:flutter/material.dart'; +import 'connectors.dart'; +import 'indicators.dart'; +import 'timeline_node.dart'; +import 'timeline_theme.dart'; +import 'timeline_tile.dart'; + +/// How a contents displayed be into timeline. +/// +/// See also: +/// * [TimelineTileBuilder.fromStyle] +enum ContentsAlign { + /// The contents aligned end of timeline. And the opposite contents aligned + /// start of timeline. + /// + /// Example: + /// ``` + /// opposite contents | contents + /// opposite contents | contents + /// opposite contents | contents + /// ``` + basic, + + /// The contents aligned start of timeline. And the opposite contents aligned + /// end of timeline. + /// + /// Example: + /// + /// ``` + /// contents | opposite contents + /// contents | opposite contents + /// contents | opposite contents + /// ``` + reverse, + + /// The contents and opposite contents displayed alternating. + /// + /// Example: + /// ``` + /// contents | opposite contents + /// opposite contents | contents + /// contents | opposite contents + /// opposite contents | contents + /// contents | opposite contents + /// ``` + alternating, +} + +/// An enum that representing the direction the connector is connected through +/// the builder. +/// +/// See also: +/// +/// * [TimelineTileBuilder.connected], which is how the builder uses this enum +/// to connect each connector. +/// * [TimelineTileBuilder.connectedFromStyle], which is how the builder uses +/// this enum to connect each connector. +enum ConnectionDirection { before, after } + +/// An enum that representing the connector type in [TimelineNode]. +/// +/// For example, if the timeline direction is Axis.horizontal and the text +/// direction is LTR: +/// ``` +/// start end +/// ---- O ---- +/// ``` +/// See also: +/// +/// * [ConnectedConnectorBuilder], which is use this. +enum ConnectorType { start, end } + +/// An enum that determines the style of indicator in timeline tile builder. +/// +/// TODO: replace with class to support parameters +/// +/// See also: +/// +/// * [TimelineTileBuilder.fromStyle], which is use this. +/// * [TimelineTileBuilder.connectedFromStyle], which is use this. +enum IndicatorStyle { + /// Draw dot indicator. + dot, + + /// Draw outlined dot indicator. + outlined, + + /// Draw container indicator. TODO: need child to builds... + container, + + /// Draw transparent indicator. (invisible indicator) + transparent, +} + +/// Types of connectors displayed into timeline +/// +/// See also: +/// * [TimelineTileBuilder.fromStyle]. +enum ConnectorStyle { + /// Draw solid line connector. + solidLine, + + /// Draw dashed line connector. + dashedLine, + + /// Draw transparent connector. (invisible connector) + transparent, +} + +/// Signature for a function that creates a connected connector widget for a +/// given index and type, e.g., in a timeline tile builder. +typedef ConnectedConnectorBuilder = Widget? Function( + BuildContext context, int index, ConnectorType type); + +/// Signature for a function that creates a typed value for a given index, e.g., +/// in a timeline tile builder. +/// +/// Used by [TimelineTileBuilder] that use lazily-generated typed value. +typedef IndexedValueBuilder = T Function(BuildContext context, int index); + +/// WARNING: The interface of this class is not yet clear. It may change +/// frequently. +/// +/// A delegate that supplies [TimelineTile] for timeline using a builder +/// callback. +class TimelineTileBuilder { + /// Create a connected tile builder, which builds tiles using each component + /// builder. + /// + /// Check below for how to build: + /// + /// Original build system: + /// + /// ``` + /// | <-- builder(0) + /// O contents1 <-- builder(0) + /// | <-- builder(0) + /// | <-- builder(1) + /// O contents2 <-- builder(1) + /// | <-- builder(1) + /// ``` + /// + /// Connected build system(before): + /// + /// ``` + /// | <-- draw if provided [firstConnectorBuilder] + /// O contents1 <-- builder(0) + /// | <-- builder(1) + /// | <-- builder(1) + /// O contents2 <-- builder(1) + /// | <-- builder(2) + /// | <-- builder(2) + /// O <-- builder(2) + /// | <-- builder(3) + /// .. + /// | <-- draw if provided [lastConnectorBuilder] + /// ``` + /// + /// + /// Connected build system(after): + /// + /// ``` + /// | <-- draw if provided [firstConnectorBuilder] + /// O contents1 <-- builder(0) + /// | <-- builder(0) + /// | <-- builder(0) + /// O contents2 <-- builder(1) + /// | <-- builder(1) + /// | <-- builder(1) + /// O <-- builder(2) + /// | <-- builder(2) + /// .. + /// | <-- draw if provided [lastConnectorBuilder] + /// ``` + /// + /// The above example can be made similar by just set the + /// [TimelineNode.indicatorPosition] as 0 or 1, but the contents position may + /// be limited. + /// + /// {@macro timelines.itemExtentBuilder} + /// + /// See also: + /// + /// * [TimelineTileBuilder.connectedFromStyle], which builds connected tiles + /// from style. + factory TimelineTileBuilder.connected({ + required int itemCount, + ContentsAlign contentsAlign = ContentsAlign.basic, + ConnectionDirection connectionDirection = ConnectionDirection.after, + NullableIndexedWidgetBuilder? contentsBuilder, + NullableIndexedWidgetBuilder? oppositeContentsBuilder, + NullableIndexedWidgetBuilder? indicatorBuilder, + ConnectedConnectorBuilder? connectorBuilder, + WidgetBuilder? firstConnectorBuilder, + WidgetBuilder? lastConnectorBuilder, + double? itemExtent, + IndexedValueBuilder? itemExtentBuilder, + IndexedValueBuilder? nodePositionBuilder, + IndexedValueBuilder? indicatorPositionBuilder, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + }) => + TimelineTileBuilder( + itemCount: itemCount, + contentsAlign: contentsAlign, + contentsBuilder: contentsBuilder, + oppositeContentsBuilder: oppositeContentsBuilder, + indicatorBuilder: indicatorBuilder, + startConnectorBuilder: _createConnectedStartConnectorBuilder( + connectionDirection: connectionDirection, + firstConnectorBuilder: firstConnectorBuilder, + connectorBuilder: connectorBuilder, + ), + endConnectorBuilder: _createConnectedEndConnectorBuilder( + connectionDirection: connectionDirection, + lastConnectorBuilder: lastConnectorBuilder, + connectorBuilder: connectorBuilder, + itemCount: itemCount, + ), + itemExtent: itemExtent, + itemExtentBuilder: itemExtentBuilder, + nodePositionBuilder: nodePositionBuilder, + indicatorPositionBuilder: indicatorPositionBuilder, + ); + + /// Create a connected tile builder, which builds tiles using each style. + /// + /// {@macro timelines.itemExtentBuilder} + /// + /// See also: + /// + /// * [TimelineTileBuilder.connected], which builds connected tiles. + /// * [TimelineTileBuilder.fromStyle], which builds tiles from style. + factory TimelineTileBuilder.connectedFromStyle({ + @required required int itemCount, + ConnectionDirection connectionDirection = ConnectionDirection.after, + NullableIndexedWidgetBuilder? contentsBuilder, + NullableIndexedWidgetBuilder? oppositeContentsBuilder, + ContentsAlign contentsAlign = ContentsAlign.basic, + IndexedValueBuilder? indicatorStyleBuilder, + IndexedValueBuilder? connectorStyleBuilder, + ConnectorStyle firstConnectorStyle = ConnectorStyle.solidLine, + ConnectorStyle lastConnectorStyle = ConnectorStyle.solidLine, + double? itemExtent, + IndexedValueBuilder? itemExtentBuilder, + IndexedValueBuilder? nodePositionBuilder, + IndexedValueBuilder? indicatorPositionBuilder, + }) => + TimelineTileBuilder( + itemCount: itemCount, + contentsAlign: contentsAlign, + contentsBuilder: contentsBuilder, + oppositeContentsBuilder: oppositeContentsBuilder, + indicatorBuilder: (BuildContext context, int index) => + _createStyledIndicatorBuilder( + indicatorStyleBuilder?.call(context, index))(context), + startConnectorBuilder: _createConnectedStartConnectorBuilder( + connectionDirection: connectionDirection, + firstConnectorBuilder: (BuildContext context) => + _createStyledConnectorBuilder(firstConnectorStyle)(context), + connectorBuilder: (BuildContext context, int index, __) => + _createStyledConnectorBuilder( + connectorStyleBuilder?.call(context, index))(context), + ), + endConnectorBuilder: _createConnectedEndConnectorBuilder( + connectionDirection: connectionDirection, + lastConnectorBuilder: (BuildContext context) => + _createStyledConnectorBuilder(lastConnectorStyle)(context), + connectorBuilder: (BuildContext context, int index, __) => + _createStyledConnectorBuilder( + connectorStyleBuilder?.call(context, index))(context), + itemCount: itemCount, + ), + itemExtent: itemExtent, + itemExtentBuilder: itemExtentBuilder, + nodePositionBuilder: nodePositionBuilder, + indicatorPositionBuilder: indicatorPositionBuilder, + ); + + /// Create a tile builder, which builds tiles using each style. + /// + /// {@macro timelines.itemExtentBuilder} + /// TODO: style each index like fromStyleBuilder + /// + /// See also: + /// + /// * [IndicatorStyle] + /// * [ConnectorStyle] + /// * [ContentsAlign] + factory TimelineTileBuilder.fromStyle({ + required int itemCount, + NullableIndexedWidgetBuilder? contentsBuilder, + NullableIndexedWidgetBuilder? oppositeContentsBuilder, + ContentsAlign contentsAlign = ContentsAlign.basic, + IndicatorStyle indicatorStyle = IndicatorStyle.dot, + ConnectorStyle connectorStyle = ConnectorStyle.solidLine, + ConnectorStyle endConnectorStyle = ConnectorStyle.solidLine, + double? itemExtent, + IndexedValueBuilder? itemExtentBuilder, + IndexedValueBuilder? nodePositionBuilder, + IndexedValueBuilder? indicatorPositionBuilder, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + }) => + TimelineTileBuilder( + itemCount: itemCount, + contentsAlign: contentsAlign, + contentsBuilder: contentsBuilder, + oppositeContentsBuilder: oppositeContentsBuilder, + indicatorBuilder: (BuildContext context, int index) => + _createStyledIndicatorBuilder(indicatorStyle)(context), + startConnectorBuilder: (BuildContext context, _) => + _createStyledConnectorBuilder(connectorStyle)(context), + endConnectorBuilder: (BuildContext context, _) => + _createStyledConnectorBuilder(connectorStyle)(context), + itemExtent: itemExtent, + itemExtentBuilder: itemExtentBuilder, + nodePositionBuilder: nodePositionBuilder, + indicatorPositionBuilder: indicatorPositionBuilder, + ); + + /// Create a tile builder, which builds tiles using each component builder. + /// + /// {@template timelines.itemExtentBuilder} + /// If each item has a fixed extent, use [itemExtent], and if each item has a + /// different extent, use [itemExtentBuilder]. + /// {@endtemplate} + /// + /// TODO: need refactoring, is it has many builders...? + factory TimelineTileBuilder({ + required int itemCount, + ContentsAlign contentsAlign = ContentsAlign.basic, + NullableIndexedWidgetBuilder? contentsBuilder, + NullableIndexedWidgetBuilder? oppositeContentsBuilder, + NullableIndexedWidgetBuilder? indicatorBuilder, + NullableIndexedWidgetBuilder? startConnectorBuilder, + NullableIndexedWidgetBuilder? endConnectorBuilder, + double? itemExtent, + IndexedValueBuilder? itemExtentBuilder, + IndexedValueBuilder? nodePositionBuilder, + IndexedValueBuilder? nodeItemOverlapBuilder, + IndexedValueBuilder? indicatorPositionBuilder, + IndexedValueBuilder? themeBuilder, + }) { + assert( + itemExtent == null || itemExtentBuilder == null, + 'Cannot provide both a itemExtent and a itemExtentBuilder.', + ); + + final NullableIndexedWidgetBuilder effectiveContentsBuilder = + _createAlignedContentsBuilder( + align: contentsAlign, + contentsBuilder: contentsBuilder, + oppositeContentsBuilder: oppositeContentsBuilder, + ); + final NullableIndexedWidgetBuilder effectiveOppositeContentsBuilder = + _createAlignedContentsBuilder( + align: contentsAlign, + contentsBuilder: oppositeContentsBuilder, + oppositeContentsBuilder: contentsBuilder, + ); + + return TimelineTileBuilder._( + (BuildContext context, int index) { + final TimelineTile tile = TimelineTile( + mainAxisExtent: itemExtent ?? itemExtentBuilder?.call(context, index), + node: TimelineNode( + indicator: indicatorBuilder?.call(context, index) ?? + Indicator.transparent(), + startConnector: startConnectorBuilder?.call(context, index), + endConnector: endConnectorBuilder?.call(context, index), + overlap: nodeItemOverlapBuilder?.call(context, index), + position: nodePositionBuilder?.call(context, index), + indicatorPosition: indicatorPositionBuilder?.call(context, index), + ), + contents: effectiveContentsBuilder(context, index), + oppositeContents: effectiveOppositeContentsBuilder(context, index), + ); + + final TimelineThemeData? theme = themeBuilder?.call(context, index); + if (theme != null) { + return TimelineTheme( + data: theme, + child: tile, + ); + } else { + return tile; + } + }, + itemCount: itemCount, + ); + } + + const TimelineTileBuilder._( + this._builder, { + required this.itemCount, + }) : assert(itemCount >= 0); + + final IndexedWidgetBuilder _builder; + final int itemCount; + + Widget build(BuildContext context, int index) => _builder(context, index); + + static NullableIndexedWidgetBuilder _createConnectedStartConnectorBuilder({ + ConnectionDirection? connectionDirection, + WidgetBuilder? firstConnectorBuilder, + ConnectedConnectorBuilder? connectorBuilder, + }) => + (BuildContext context, int index) { + if (index == 0) { + if (firstConnectorBuilder != null) { + return firstConnectorBuilder.call(context); + } else { + return null; + } + } + + if (connectionDirection == ConnectionDirection.before) { + return connectorBuilder?.call(context, index, ConnectorType.start); + } else { + return connectorBuilder?.call( + context, index - 1, ConnectorType.start); + } + }; + + static NullableIndexedWidgetBuilder _createConnectedEndConnectorBuilder({ + required int itemCount, + ConnectionDirection? connectionDirection, + WidgetBuilder? lastConnectorBuilder, + ConnectedConnectorBuilder? connectorBuilder, + }) => + (BuildContext context, int index) { + if (index == itemCount - 1) { + if (lastConnectorBuilder != null) { + return lastConnectorBuilder.call(context); + } else { + return null; + } + } + + if (connectionDirection == ConnectionDirection.before) { + return connectorBuilder?.call(context, index + 1, ConnectorType.end); + } else { + return connectorBuilder?.call(context, index, ConnectorType.end); + } + }; + + static NullableIndexedWidgetBuilder _createAlignedContentsBuilder({ + required ContentsAlign align, + NullableIndexedWidgetBuilder? contentsBuilder, + NullableIndexedWidgetBuilder? oppositeContentsBuilder, + }) => + (BuildContext context, int index) { + switch (align) { + case ContentsAlign.alternating: + if (index.isOdd) { + return oppositeContentsBuilder?.call(context, index); + } + + return contentsBuilder?.call(context, index); + case ContentsAlign.reverse: + return oppositeContentsBuilder?.call(context, index); + case ContentsAlign.basic: + default: + return contentsBuilder?.call(context, index); + } + }; + + static WidgetBuilder _createStyledIndicatorBuilder(IndicatorStyle? style) => + (_) { + switch (style) { + case IndicatorStyle.dot: + return Indicator.dot(); + case IndicatorStyle.outlined: + return Indicator.outlined(); + case IndicatorStyle.container: + return Indicator.widget(); + case IndicatorStyle.transparent: + default: + return Indicator.transparent(); + } + }; + + static WidgetBuilder _createStyledConnectorBuilder(ConnectorStyle? style) => + (_) { + switch (style) { + case ConnectorStyle.solidLine: + return Connector.solidLine(); + case ConnectorStyle.dashedLine: + return Connector.dashedLine(); + case ConnectorStyle.transparent: + default: + return Connector.transparent(); + } + }; +} + +int _kDefaultSemanticIndexCallback(Widget _, int localIndex) => localIndex; + +/// The widgets returned from the builder callback are automatically wrapped in +/// [AutomaticKeepAlive] widgets if [addAutomaticKeepAlives] is true +/// (the default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is +/// true (also the default). +/// +/// ## Accessibility +/// +/// The [CustomScrollView] requires that its semantic children are annotated +/// using [IndexedSemantics]. This is done by default in the delegate with the +/// `addSemanticIndexes` parameter set to true. +/// +/// If multiple delegates are used in a single scroll view, then the indexes +/// will not be correct by default. The `semanticIndexOffset` can be used to +/// offset the semantic indexes of each delegate so that the indexes are +/// monotonically increasing. For example, if a scroll view contains two +/// delegates where the first has 10 children contributing semantics, then the +/// second delegate should offset its children by 10. +/// +/// See also: +/// +/// * [IndexedSemantics], for an example of manually annotating child nodes +/// with semantic indexes. +class TimelineTileBuilderDelegate extends SliverChildBuilderDelegate { + TimelineTileBuilderDelegate( + NullableIndexedWidgetBuilder builder, { + ChildIndexGetter? findChildIndexCallback, + int? childCount, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + SemanticIndexCallback semanticIndexCallback = + _kDefaultSemanticIndexCallback, + int semanticIndexOffset = 0, + }) : super( + builder, + findChildIndexCallback: findChildIndexCallback, + childCount: childCount, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + semanticIndexCallback: semanticIndexCallback, + semanticIndexOffset: semanticIndexOffset, + ); +} diff --git a/packages/deriv_ui/lib/components/timeline/src/timelines.dart b/packages/deriv_ui/lib/components/timeline/src/timelines.dart new file mode 100644 index 000000000..fa9d61db0 --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/src/timelines.dart @@ -0,0 +1,483 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +import 'timeline_theme.dart'; +import 'timeline_tile_builder.dart'; + +/// A scrollable timeline of widgets arranged linearly. +class Timeline extends BoxScrollView { + /// Creates a scrollable, linear array of widgets that are created on demand. + /// + /// This constructor is appropriate for list views with a large (or infinite) + /// number of children because the builder is called only for those children + /// that are actually visible. + /// + /// Providing a non-null `itemCount` improves the ability of the [Timeline] to + /// estimate the maximum scroll extent. + /// + /// The `itemBuilder` callback will be called only with indices greater than + /// or equal to zero and less than `itemCount`. + /// + /// The `itemBuilder` should always return a non-null widget, and actually + /// create the widget instances when called. + /// Avoid using a builder that returns a previously-constructed widget; if the + /// timeline view's children are created in advance, or all at once when the + /// [Timeline] itself is created, it is more efficient to use the [Timeline] + /// constructor. Even more efficient, however, is to create the instances on + /// demand using this constructor's `itemBuilder` callback. + /// + /// The `addAutomaticKeepAlives` argument corresponds to the + /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The + /// `addRepaintBoundaries` argument corresponds to the + /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The + /// `addSemanticIndexes` argument corresponds to the + /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None + /// may be null. + /// + /// [Timeline.builder] by default does not support child reordering. If you + /// are planning to change child order at a + /// later time, consider using [Timeline] or [Timeline.custom]. + factory Timeline.tileBuilder({ + required TimelineTileBuilder builder, Key? key, + Axis? scrollDirection, + bool reverse = false, + ScrollController? controller, + bool? primary, + ScrollPhysics? physics, + bool shrinkWrap = false, + EdgeInsetsGeometry? padding, + // double itemExtent, TODO: fixedExtentTileBuilder? + double? cacheExtent, + int? semanticChildCount, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, + ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = + ScrollViewKeyboardDismissBehavior.manual, + String? restorationId, + Clip clipBehavior = Clip.hardEdge, + TimelineThemeData? theme, + }) { + assert(builder.itemCount >= 0); + assert( + semanticChildCount == null || semanticChildCount <= builder.itemCount); + return Timeline.custom( + key: key, + childrenDelegate: SliverChildBuilderDelegate( + builder.build, + childCount: builder.itemCount, + // TODO: apply some fields if needed. + ), + scrollDirection: scrollDirection, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + shrinkWrap: shrinkWrap, + padding: padding, + // itemExtent: itemExtent, + cacheExtent: cacheExtent, + semanticChildCount: semanticChildCount ?? builder.itemCount, + dragStartBehavior: dragStartBehavior, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + clipBehavior: clipBehavior, + theme: theme, + ); + } + + /// Creates a scrollable, linear array of widgets from an explicit [List]. + /// + /// This constructor is appropriate for timeline views with a small number of + /// children because constructing the [List] requires doing work for every + /// child that could possibly be displayed in the timeline view instead of + /// just those children that are actually visible. + /// + /// It is usually more efficient to create children on demand using + /// [Timeline.builder] because it will create the widget children lazily as + /// necessary. + /// + /// The `addAutomaticKeepAlives` argument corresponds to the + /// [SliverChildListDelegate.addAutomaticKeepAlives] property. The + /// `addRepaintBoundaries` argument corresponds to the + /// [SliverChildListDelegate.addRepaintBoundaries] property. The + /// `addSemanticIndexes` argument corresponds to the + /// [SliverChildListDelegate.addSemanticIndexes] property. None may be null. + Timeline({ + Key? key, + Axis? scrollDirection, + bool reverse = false, + ScrollController? controller, + bool? primary, + ScrollPhysics? physics, + bool shrinkWrap = false, + EdgeInsetsGeometry? padding, + this.itemExtent, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + double? cacheExtent, + List children = const [], + int? semanticChildCount, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, + ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = + ScrollViewKeyboardDismissBehavior.manual, + String? restorationId, + Clip clipBehavior = Clip.hardEdge, + this.theme, + }) : childrenDelegate = SliverChildListDelegate( + children, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + ), + assert(scrollDirection == null || theme == null, + 'Cannot provide both a scrollDirection and a theme.'), + super( + key: key, + scrollDirection: scrollDirection ?? theme?.direction ?? Axis.vertical, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + shrinkWrap: shrinkWrap, + padding: padding, + cacheExtent: cacheExtent, + semanticChildCount: semanticChildCount ?? children.length, + dragStartBehavior: dragStartBehavior, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + clipBehavior: clipBehavior, + ); + + /// Creates a scrollable, linear array of widgets that are created on demand. + /// + /// This constructor is appropriate for list views with a large (or infinite) + /// number of children because the builder is called only for those children + /// that are actually visible. + /// + /// Providing a non-null `itemCount` improves the ability of the [Timeline] to + /// estimate the maximum scroll extent. + /// + /// The `itemBuilder` callback will be called only with indices greater than + /// or equal to zero and less than `itemCount`. + /// + /// The `itemBuilder` should always return a non-null widget, and actually + /// create the widget instances when called. + /// Avoid using a builder that returns a previously-constructed widget; if the + /// timeline view's children are created in advance, or all at once when the + /// [Timeline] itself is created, it is more efficient to use the [Timeline] + /// constructor. Even more efficient, however, is to create the instances on + /// demand using this constructor's `itemBuilder` callback. + /// + /// The `addAutomaticKeepAlives` argument corresponds to the + /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The + /// `addRepaintBoundaries` argument corresponds to the + /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The + /// `addSemanticIndexes` argument corresponds to the + /// [SliverChildBuilderDelegate.addSemanticIndexes] property. None + /// may be null. + /// + /// [Timeline.builder] by default does not support child reordering. If you + /// are planning to change child order at a + /// later time, consider using [Timeline] or [Timeline.custom]. + Timeline.builder({ + required IndexedWidgetBuilder itemBuilder, required int itemCount, Key? key, + Axis? scrollDirection, + bool reverse = false, + ScrollController? controller, + bool? primary, + ScrollPhysics? physics, + bool shrinkWrap = false, + EdgeInsetsGeometry? padding, + this.itemExtent, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + double? cacheExtent, + int? semanticChildCount, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, + ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = + ScrollViewKeyboardDismissBehavior.manual, + String? restorationId, + Clip clipBehavior = Clip.hardEdge, + this.theme, + }) : assert(itemCount >= 0), + assert(semanticChildCount == null || semanticChildCount <= itemCount), + assert(scrollDirection == null || theme == null, + 'Cannot provide both a scrollDirection and a theme.'), + childrenDelegate = SliverChildBuilderDelegate( + itemBuilder, + childCount: itemCount, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + ), + super( + key: key, + scrollDirection: scrollDirection ?? theme?.direction ?? Axis.vertical, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + shrinkWrap: shrinkWrap, + padding: padding, + cacheExtent: cacheExtent, + semanticChildCount: semanticChildCount ?? itemCount, + dragStartBehavior: dragStartBehavior, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + clipBehavior: clipBehavior, + ); + + /// Creates a scrollable, linear array of widgets with a custom child model. + /// + /// For example, a custom child model can control the algorithm used to + /// estimate the size of children that are not actually visible. + /// + /// See also: + /// + /// * This works similarly to [ListView.custom]. + Timeline.custom({ + required this.childrenDelegate, Key? key, + Axis? scrollDirection, + bool reverse = false, + ScrollController? controller, + bool? primary, + ScrollPhysics? physics, + bool shrinkWrap = false, + EdgeInsetsGeometry? padding, + this.itemExtent, + double? cacheExtent, + int? semanticChildCount, + DragStartBehavior dragStartBehavior = DragStartBehavior.start, + ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = + ScrollViewKeyboardDismissBehavior.manual, + String? restorationId, + Clip clipBehavior = Clip.hardEdge, + this.theme, + }) : assert(scrollDirection == null || theme == null, + 'Cannot provide both a scrollDirection and a theme.'), + super( + key: key, + scrollDirection: scrollDirection ?? theme?.direction ?? Axis.vertical, + reverse: reverse, + controller: controller, + primary: primary, + physics: physics, + shrinkWrap: shrinkWrap, + padding: padding, + cacheExtent: cacheExtent, + semanticChildCount: semanticChildCount, + dragStartBehavior: dragStartBehavior, + keyboardDismissBehavior: keyboardDismissBehavior, + restorationId: restorationId, + clipBehavior: clipBehavior, + ); + + /// If non-null, forces the children to have the given extent in the scroll + /// direction. + /// + /// Specifying an [itemExtent] is more efficient than letting the children + /// determine their own extent because the scrolling machinery can make use + /// of the foreknowledge of the children's extent to save work, for example + /// when the scroll position changes drastically. + final double? itemExtent; + + /// A delegate that provides the children for the [Timeline]. + /// + /// The [Timeline.custom] constructor lets you specify this delegate + /// explicitly. The [Timeline] and [Timeline.builder] constructors create a + /// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder], + /// respectively. + final SliverChildDelegate childrenDelegate; + + /// Default visual properties, like colors, size and spaces, for this + /// timeline's component widgets. + /// + /// The default value of this property is the value of + /// [TimelineThemeData.vertical()]. + final TimelineThemeData? theme; + + @override + Widget buildChildLayout(BuildContext context) { + Widget result; + if (itemExtent != null) { + result = SliverFixedExtentList( + delegate: childrenDelegate, + itemExtent: itemExtent!, + ); + } else { + result = SliverList(delegate: childrenDelegate); + } + + TimelineThemeData? theme; + if (this.theme != null) { + theme = this.theme; + } else if (scrollDirection != TimelineTheme.of(context).direction) { + theme = TimelineTheme.of(context).copyWith(direction: scrollDirection); + } + + if (theme != null) { + return TimelineTheme( + data: theme, + child: result, + ); + } else { + return result; + } + } +} + +/// A widget that displays its children in a one-dimensional array with +/// timeline theme. +class FixedTimeline extends StatelessWidget { + /// Creates a timeline flex layout. + factory FixedTimeline.tileBuilder({ + required TimelineTileBuilder builder, + TimelineThemeData? theme, + Axis? direction, + MainAxisSize mainAxisSize = MainAxisSize.max, + TextDirection? textDirection, + VerticalDirection verticalDirection = VerticalDirection.down, + Clip clipBehavior = Clip.none, + }) => + // TODO: how remove Builders? + FixedTimeline( + children: [ + for (int i = 0; i < builder.itemCount; i++) + Builder( + builder: (BuildContext context) => builder.build(context, i), + ), + ], + theme: theme, + direction: direction, + mainAxisSize: mainAxisSize, + textDirection: textDirection, + verticalDirection: verticalDirection, + clipBehavior: clipBehavior, + ); + + /// Creates a timeline flex layout. + /// + /// The [direction], [verticalDirection] arguments must not be null. + /// + /// The [textDirection] argument defaults to the ambient [Directionality], + /// if any. If there is no ambient directionality, and a text direction is + /// going to be necessary to decide which direction to lay the children in or + /// to disambiguate `start` or `end` values for the main or cross axis + /// directions, the [textDirection] must not be null. + const FixedTimeline({ + Key? key, + this.theme, + this.direction, + this.mainAxisSize = MainAxisSize.max, + this.textDirection, + this.verticalDirection = VerticalDirection.down, + this.clipBehavior = Clip.none, + this.children = const [], + }) : assert(direction == null || theme == null, + 'Cannot provide both a direction and a theme.'), + super(key: key); + + /// Default visual properties, like colors, size and spaces, for this + /// timeline's component widgets. + /// + /// The default value of this property is the value of + /// [TimelineThemeData.vertical()]. + final TimelineThemeData? theme; + + /// The direction to use as the main axis. + final Axis? direction; + + /// The widgets below this widget in the tree. + /// + /// If this list is going to be mutated, it is usually wise to put a [Key] on + /// each of the child widgets, so that the framework can match old + /// configurations to new configurations and maintain the underlying + /// render objects. + /// + /// See also: + /// + /// * [MultiChildRenderObjectWidget.children] + final List children; + + /// How much space should be occupied in the main axis. + /// + /// After allocating space to children, there might be some remaining free + /// space. This value controls whether to maximize or minimize the amount of + /// free space, subject to the incoming layout constraints. + /// + /// If some children have a non-zero flex factors (and none have a fit of + /// [FlexFit.loose]), they will expand to consume all the available space and + /// there will be no remaining free space to maximize or minimize, making this + /// value irrelevant to the final layout. + final MainAxisSize mainAxisSize; + + /// Determines the order to lay children out horizontally and how to interpret + /// `start` and `end` in the horizontal direction. + /// + /// Defaults to the ambient [Directionality]. + /// + /// If [textDirection] is [TextDirection.rtl], then the direction in which + /// text flows starts from right to left. Otherwise, if [textDirection] is + /// [TextDirection.ltr], then the direction in which text flows starts from + /// left to right. + /// + /// If the [direction] is [Axis.horizontal], this controls the order in which + /// the children are positioned (left-to-right or right-to-left). + /// + /// If the [direction] is [Axis.horizontal], and there's more than one child, + /// then the [textDirection] (or the ambient [Directionality]) must not + /// be null. + final TextDirection? textDirection; + + /// Determines the order to lay children out vertically and how to interpret + /// `start` and `end` in the vertical direction. + /// + /// Defaults to [VerticalDirection.down]. + /// + /// If the [direction] is [Axis.vertical], there's more than one child, then + /// the [verticalDirection] must not be null. + final VerticalDirection verticalDirection; + + /// The content will be clipped (or not) according to this option. + /// + /// See the enum Clip for details of all possible options and their common + /// use cases. + /// + /// Defaults to [Clip.none]. + final Clip clipBehavior; + + @override + Widget build(BuildContext context) { + final Axis direction = + this.direction ?? this.theme?.direction ?? Axis.vertical; + + final Widget result = Flex( + direction: direction, + children: children, + mainAxisSize: mainAxisSize, + textDirection: textDirection, + verticalDirection: verticalDirection, + clipBehavior: clipBehavior, + ); + + TimelineThemeData? theme; + if (this.direction != null) { + if (direction != TimelineTheme.of(context).direction) { + theme = TimelineTheme.of(context).copyWith(direction: this.direction); + } + } else if (this.theme != null) { + theme = this.theme; + } + + if (theme != null) { + return TimelineTheme( + data: theme, + child: result, + ); + } else { + return result; + } + } +} diff --git a/packages/deriv_ui/lib/components/timeline/src/util.dart b/packages/deriv_ui/lib/components/timeline/src/util.dart new file mode 100644 index 000000000..e331f567a --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/src/util.dart @@ -0,0 +1,2 @@ + +const double kFlexMultiplier = 1000; diff --git a/packages/deriv_ui/lib/components/timeline/timelines.dart b/packages/deriv_ui/lib/components/timeline/timelines.dart new file mode 100644 index 000000000..1b65013cb --- /dev/null +++ b/packages/deriv_ui/lib/components/timeline/timelines.dart @@ -0,0 +1,12 @@ +/// Widgets that make it easy to implement the timeline UI component. +library timelines; + +export 'src/connector_theme.dart'; +export 'src/connectors.dart'; +export 'src/indicator_theme.dart'; +export 'src/indicators.dart'; +export 'src/timelines.dart'; +export 'src/timeline_node.dart'; +export 'src/timeline_theme.dart'; +export 'src/timeline_tile.dart'; +export 'src/timeline_tile_builder.dart'; diff --git a/packages/deriv_ui/pubspec.yaml b/packages/deriv_ui/pubspec.yaml index 08fa0368b..acdf81391 100644 --- a/packages/deriv_ui/pubspec.yaml +++ b/packages/deriv_ui/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_ui description: A new Flutter package project. -version: 0.0.7+9 +version: 0.0.8+1 publish_to: none environment: diff --git a/packages/deriv_widgetbook/CHANGELOG.md b/packages/deriv_widgetbook/CHANGELOG.md index 5163095e0..da7b68d4c 100644 --- a/packages/deriv_widgetbook/CHANGELOG.md +++ b/packages/deriv_widgetbook/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.0.2+11 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 0.0.2+10 + + - Update a dependency to the latest release. + ## 0.0.2+9 - Update a dependency to the latest release. diff --git a/packages/deriv_widgetbook/pubspec.yaml b/packages/deriv_widgetbook/pubspec.yaml index 79424c1a1..919c759af 100644 --- a/packages/deriv_widgetbook/pubspec.yaml +++ b/packages/deriv_widgetbook/pubspec.yaml @@ -1,7 +1,7 @@ name: deriv_widgetbook description: Storybook for Deriv UI widgets and components. publish_to: "none" -version: 0.0.2+9 +version: 0.0.2+11 environment: sdk: ">=3.0.0 <4.0.0" @@ -16,7 +16,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_ui - ref: deriv_ui-v0.0.7+9 + ref: deriv_ui-v0.0.8+1 deriv_theme: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git diff --git a/packages/update_checker/CHANGELOG.md b/packages/update_checker/CHANGELOG.md index caf0d3f95..9d0d5fc46 100644 --- a/packages/update_checker/CHANGELOG.md +++ b/packages/update_checker/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.5.1 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 1.5.0 + + - **FEAT**(deriv_ui): [DERG 2450] Added Timeline Widget to Deriv UI ([#631](https://github.com/regentmarkets/flutter-deriv-packages/issues/631)). ([e34d78b3](https://github.com/regentmarkets/flutter-deriv-packages/commit/e34d78b303358cb5f91abab14a2a042ce3650b0f)) + ## 1.4.0 - **FEAT**(update_checker): add the ability to change the key from the app side ([#628](https://github.com/regentmarkets/flutter-deriv-packages/issues/628)). ([b18609a0](https://github.com/regentmarkets/flutter-deriv-packages/commit/b18609a00533aaab6d6962eb89f323e2f560df8b)) diff --git a/packages/update_checker/example/lib/update_bloc_page.dart b/packages/update_checker/example/lib/update_bloc_page.dart index 55b0b079f..761cc61a6 100644 --- a/packages/update_checker/example/lib/update_bloc_page.dart +++ b/packages/update_checker/example/lib/update_bloc_page.dart @@ -10,7 +10,7 @@ class UpdateBlocPage extends StatefulWidget { class _UpdateBlocPageState extends State { final UpdateBloc updateBloc = - UpdateBloc(firebaseRepository: const FirebaseRemoteConfigRepository()); + UpdateBloc(firebaseRepository: FirebaseRemoteConfigRepository()); @override Widget build(BuildContext context) => Scaffold( diff --git a/packages/update_checker/example/lib/update_checker_page.dart b/packages/update_checker/example/lib/update_checker_page.dart index 1d54f4043..672a432a0 100644 --- a/packages/update_checker/example/lib/update_checker_page.dart +++ b/packages/update_checker/example/lib/update_checker_page.dart @@ -10,7 +10,7 @@ class UpdateCheckerPage extends StatelessWidget { ), body: Builder( builder: (BuildContext context) => UpdateChecker( - firebaseRepository: const FirebaseRemoteConfigRepository(), + firebaseRepository: FirebaseRemoteConfigRepository(), onNotAvailable: () => _showSnackBar( context, 'Update not available', diff --git a/packages/update_checker/pubspec.yaml b/packages/update_checker/pubspec.yaml index 5d50c0fc9..ac5909996 100644 --- a/packages/update_checker/pubspec.yaml +++ b/packages/update_checker/pubspec.yaml @@ -1,6 +1,6 @@ name: update_checker description: Check and retrieve update information from the server for the given package. -version: 1.4.0 +version: 1.5.1 homepage: https://deriv.com/ publish_to: "none"