From c0cb7e57257be13504516600d7fee3335891a37a Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Tue, 23 Sep 2025 14:58:38 +0800 Subject: [PATCH 01/14] feat: upgrade Flutter to version 3.24.1 - Update Flutter version constraint from >=3.10.1 to >=3.24.1 - Update CI/CD workflow to use Flutter 3.24.1 - Upgrade all dependencies to latest compatible versions - Fix Android build configuration: - Update Gradle from 7.6 to 8.0 - Update Android Gradle Plugin from 7.3.0 to 8.1.4 - Update Kotlin from 1.7.21 to 1.9.22 - Update compileSdkVersion and targetSdkVersion to 35 - Add namespace declaration for new AGP requirements - Add JVM compatibility settings - Add android:exported=true to MainActivity - Fix breaking changes: - Replace MaterialState with WidgetState APIs - Update showBottomSheet generic type usage - Add required landingCompany parameter to ActiveSymbolsRequest - Remove unnecessary dart:ui imports - All tests passing (104/104) - Android build verified successful --- .github/workflows/main.yaml | 2 +- example/android/app/build.gradle | 16 +++++++++++++--- .../android/app/src/main/AndroidManifest.xml | 1 + example/android/build.gradle | 4 ++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/lib/main.dart | 6 +++--- example/pubspec.yaml | 11 ++++++----- .../deriv_chart/chart/helpers/indicator.dart | 2 -- .../theme/painting_styles/barrier_style.dart | 2 -- lib/src/theme/painting_styles/grid_style.dart | 1 - lib/src/theme/text_styles.dart | 2 -- .../market_selector_button.dart | 12 ++++++------ pubspec.yaml | 18 +++++++++--------- 13 files changed, 42 insertions(+), 37 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 360d1afd6..dac3acc12 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -23,7 +23,7 @@ jobs: - name: 🐦 Setup Flutter uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa with: - flutter-version: "3.10.2" + flutter-version: "3.24.1" channel: stable cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 457c08554..2be262660 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -26,7 +26,17 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 33 + namespace 'com.example.chart_attempt' + compileSdkVersion 35 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -39,8 +49,8 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.chart_attempt" - minSdkVersion 16 - targetSdkVersion 28 + minSdkVersion flutter.minSdkVersion + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 2ee70cb26..8f6cfc22c 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ android:icon="@mipmap/ic_launcher"> { Asset _symbol = Asset(name: 'R_50'); final ChartController _controller = ChartController(); - PersistentBottomSheetController? _bottomSheetController; + PersistentBottomSheetController? _bottomSheetController; late PrefServiceCache _prefService; @@ -219,7 +219,7 @@ class _FullscreenChartState extends State { Future _getActiveSymbols() async { _activeSymbols = (await ActiveSymbolsResponse.fetchActiveSymbols( - const ActiveSymbolsRequest(activeSymbols: 'brief', productType: 'basic'), + const ActiveSymbolsRequest(activeSymbols: 'brief', productType: 'basic', landingCompany: 'svg'), )) .activeSymbols!; @@ -660,7 +660,7 @@ class _FullscreenChartState extends State { Widget _buildMarketSelectorButton() => MarketSelectorButton( asset: _symbol, onTap: () { - _bottomSheetController = showBottomSheet( + _bottomSheetController = showBottomSheet( backgroundColor: Colors.transparent, context: context, builder: (BuildContext context) => MarketSelector( diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 89fc5ca9a..4b8056f7b 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,21 +5,22 @@ publish_to: none environment: sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.24.1" dependencies: flutter: sdk: flutter deriv_chart: path: ../ - vibration: ^1.7.6 - intl: ^0.18.0 + intl: ^0.19.0 + vibration: ^1.8.4 flutter_localizations: sdk: flutter - pref: ^2.7.1 + pref: ^2.8.0 flutter_deriv_api: git: url: git@github.com:regentmarkets/flutter-deriv-api.git - ref: flutter-version-3 + ref: v1.3.0 dev_dependencies: deriv_lint: @@ -29,7 +30,7 @@ dev_dependencies: ref: dev flutter_test: sdk: flutter - intl_utils: ^2.8.2 + intl_utils: ^2.8.7 flutter: uses-material-design: true diff --git a/lib/src/deriv_chart/chart/helpers/indicator.dart b/lib/src/deriv_chart/chart/helpers/indicator.dart index 8f0b150df..d36ee5e51 100644 --- a/lib/src/deriv_chart/chart/helpers/indicator.dart +++ b/lib/src/deriv_chart/chart/helpers/indicator.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:deriv_chart/src/theme/painting_styles/barrier_style.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/theme/painting_styles/barrier_style.dart b/lib/src/theme/painting_styles/barrier_style.dart index dced463d2..eb15f1007 100644 --- a/lib/src/theme/painting_styles/barrier_style.dart +++ b/lib/src/theme/painting_styles/barrier_style.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:deriv_chart/src/theme/painting_styles/chart_painting_style.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/theme/painting_styles/grid_style.dart b/lib/src/theme/painting_styles/grid_style.dart index 20f3dc2f0..b052464fc 100644 --- a/lib/src/theme/painting_styles/grid_style.dart +++ b/lib/src/theme/painting_styles/grid_style.dart @@ -1,4 +1,3 @@ -import 'dart:ui' show FontFeature; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/theme/text_styles.dart b/lib/src/theme/text_styles.dart index 76d12ea0c..47416c91a 100644 --- a/lib/src/theme/text_styles.dart +++ b/lib/src/theme/text_styles.dart @@ -1,7 +1,5 @@ // ignore_for_file: public_member_api_docs -import 'dart:ui'; - import 'package:flutter/material.dart'; /// This include all text styles according to Deriv theme guideline. diff --git a/lib/src/widgets/market_selector/market_selector_button.dart b/lib/src/widgets/market_selector/market_selector_button.dart index 3f5698b7e..aa773c4c0 100644 --- a/lib/src/widgets/market_selector/market_selector_button.dart +++ b/lib/src/widgets/market_selector/market_selector_button.dart @@ -51,14 +51,14 @@ class MarketSelectorButton extends StatelessWidget { value: theme ?? ChartDefaultDarkTheme(), child: TextButton( style: ButtonStyle( - padding: MaterialStateProperty.resolveWith( - (Set states) => const EdgeInsets.all(8), + padding: WidgetStateProperty.resolveWith( + (Set states) => const EdgeInsets.all(8), ), - backgroundColor: MaterialStateProperty.resolveWith( - (Set states) => backgroundColor, + backgroundColor: WidgetStateProperty.resolveWith( + (Set states) => backgroundColor, ), - shape: MaterialStateProperty.resolveWith( - (Set states) => RoundedRectangleBorder( + shape: WidgetStateProperty.resolveWith( + (Set states) => RoundedRectangleBorder( borderRadius: borderRadius, ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 668986287..5b9209ddd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,13 +5,13 @@ publish_to: none environment: sdk: ">=3.0.0 <4.0.0" - flutter: ">=3.10.1" + flutter: ">=3.24.1" dependencies: flutter_localizations: sdk: flutter - collection: ^1.15.0-nullsafety.4 + collection: ^1.18.0 deriv_technical_analysis: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git @@ -19,10 +19,10 @@ dependencies: ref: dev equatable: ^2.0.5 - intl: ^0.18.0 - json_annotation: ^4.8.0 - provider: ^6.0.5 - shared_preferences: ^2.1.0 + intl: ^0.19.0 + json_annotation: ^4.9.0 + provider: ^6.1.2 + shared_preferences: ^2.2.3 dev_dependencies: flutter_test: @@ -33,9 +33,9 @@ dev_dependencies: path: packages/deriv_lint ref: dev - build_runner: ^2.3.3 - intl_utils: ^2.8.2 - json_serializable: ^6.6.1 + build_runner: ^2.4.9 + intl_utils: ^2.8.7 + json_serializable: ^6.8.0 flutter: assets: From 60907eeb8dd0496bd821e59b860eb808cef38ee2 Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Tue, 23 Sep 2025 15:01:18 +0800 Subject: [PATCH 02/14] feat: add Flutter metadata file for example project - Add example/.metadata to track Flutter version and migration info - Contains Flutter revision 5874a72aa4c779a02553007c47dacbefba2374dc - Tracks project type and platform migration history --- example/.metadata | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 example/.metadata diff --git a/example/.metadata b/example/.metadata new file mode 100644 index 000000000..0c4457d62 --- /dev/null +++ b/example/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "5874a72aa4c779a02553007c47dacbefba2374dc" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + - platform: android + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' From 174732a1998efe8bc9301eacede2d91987c2978b Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Tue, 23 Sep 2025 16:16:25 +0800 Subject: [PATCH 03/14] ci: add java 17 set up step and remove ssh step --- .github/workflows/main.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index dac3acc12..0d3c5ccb6 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -20,6 +20,12 @@ jobs: - name: 📚 Git Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: ☕ Set Up Java 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: 🐦 Setup Flutter uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa with: @@ -28,11 +34,6 @@ jobs: cache: true cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} - - name: 🤫 Set SSH Key - uses: webfactory/ssh-agent@fd34b8dee206fe74b288a5e61bc95fba2f1911eb - with: - ssh-private-key: ${{secrets.SSH_PRIVATE_KEY}} - - name: 📦 Install Dependencies run: flutter pub get From d482c65330a50ad9833524dc15f206f623207dec Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Tue, 23 Sep 2025 18:08:34 +0800 Subject: [PATCH 04/14] ci: update repository refs --- example/lib/main.dart | 3 ++- example/pubspec.yaml | 2 +- .../drawing_tools/data_model/draggable_edge_point.dart | 1 + pubspec.yaml | 8 ++------ 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 20c708fc7..50747bee2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -219,7 +219,8 @@ class _FullscreenChartState extends State { Future _getActiveSymbols() async { _activeSymbols = (await ActiveSymbolsResponse.fetchActiveSymbols( - const ActiveSymbolsRequest(activeSymbols: 'brief', productType: 'basic', landingCompany: 'svg'), + const ActiveSymbolsRequest( + activeSymbols: 'brief', productType: 'basic', landingCompany: null), )) .activeSymbols!; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 4b8056f7b..42262ef0e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: dev_dependencies: deriv_lint: git: - url: git@github.com:regentmarkets/flutter-deriv-packages.git + url: https://github.com/deriv-com/flutter-deriv-packages.git path: packages/deriv_lint ref: dev flutter_test: diff --git a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart index 33c0b6a87..60fa46ce6 100644 --- a/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart +++ b/lib/src/deriv_chart/chart/data_visualization/drawing_tools/data_model/draggable_edge_point.dart @@ -13,6 +13,7 @@ part 'draggable_edge_point.g.dart'; /// For example with dots are draggable edge points for the line /// ⎯⎯⚪️⎯⎯⎯⚪️⎯⎯ @JsonSerializable() +// ignore: must_be_immutable class DraggableEdgePoint extends EdgePoint { /// Initializes DraggableEdgePoint({ diff --git a/pubspec.yaml b/pubspec.yaml index 5b9209ddd..24fe89822 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,11 +12,7 @@ dependencies: sdk: flutter collection: ^1.18.0 - deriv_technical_analysis: - git: - url: git@github.com:regentmarkets/flutter-deriv-packages.git - path: packages/deriv_technical_analysis - ref: dev + deriv_technical_analysis: ^1.1.1 equatable: ^2.0.5 intl: ^0.19.0 @@ -29,7 +25,7 @@ dev_dependencies: sdk: flutter deriv_lint: git: - url: git@github.com:regentmarkets/flutter-deriv-packages.git + url: https://github.com/deriv-com/flutter-deriv-packages.git path: packages/deriv_lint ref: dev From ecfbdacd808d16b070832a84570fc9b14619bc92 Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Tue, 23 Sep 2025 18:21:06 +0800 Subject: [PATCH 05/14] fix: update flutter_deriv_api Git URL from SSH to HTTPS - Change git@github.com:regentmarkets/flutter-deriv-api.git to HTTPS - Resolves CI permission issues with SSH key access - Enables public repository access without authentication --- example/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 42262ef0e..361925e35 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: pref: ^2.8.0 flutter_deriv_api: git: - url: git@github.com:regentmarkets/flutter-deriv-api.git + url: https://github.com/regentmarkets/flutter-deriv-api.git ref: v1.3.0 dev_dependencies: From fa2b0f8d0413a16bd96ac06e4a68f740df631eb9 Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Thu, 25 Sep 2025 12:24:42 +0800 Subject: [PATCH 06/14] chore: upgrade Flutter to 3.24.1 - Replace deriv_lint with inline analysis_options configuration - Update minimum iOS version from 11.0 to 12.0 - Upgrade dependencies: connectivity_plus, device_info_plus, package_info_plus - Remove market change reminder functionality and tests - Update iOS project configuration and Podfile - Add keystore security entries to Android .gitignore --- analysis_options.yaml | 133 ++++++++++++++- example/analysis_options.yaml | 4 - example/android/.gitignore | 6 + example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Podfile | 2 +- example/ios/Podfile.lock | 57 +++---- example/ios/Runner.xcodeproj/project.pbxproj | 15 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/ios/Runner/AppDelegate.swift | 2 +- example/lib/main.dart | 66 ++------ example/lib/settings_page.dart | 9 +- example/lib/utils/market_change_reminder.dart | 160 ------------------ example/test/market_change_reminder_test.dart | 46 ----- example/test/trading_times_mock_data.dart | 45 ----- 14 files changed, 193 insertions(+), 356 deletions(-) delete mode 100644 example/analysis_options.yaml delete mode 100644 example/lib/utils/market_change_reminder.dart delete mode 100644 example/test/market_change_reminder_test.dart delete mode 100644 example/test/trading_times_mock_data.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index f55005fb0..1d0e79c27 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,133 @@ -include: package:deriv_lint/analysis_options.yaml analyzer: exclude: - - test + - test/** + - example/** + - lib/generated/** + + language: + strict-raw-types: true + + errors: + todo: ignore + missing_required_param: warning + missing_return: warning + # https://github.com/flutter/flutter/pull/24528 + +linter: + rules: + # Taken from https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_declare_return_types + - always_put_control_body_on_new_line + - always_put_required_named_parameters_first + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catches_without_on_clauses + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + - avoid_field_initializers_in_const_classes + - avoid_function_literals_in_foreach_calls + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_print + - avoid_redundant_argument_values + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null_for_void + - avoid_setters_without_getters + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_types_as_parameter_names + # - avoid_types_on_closure_parameters + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - avoid_void_async + - await_only_futures + - camel_case_types + - cancel_subscriptions + - cascade_invocations + - close_sinks + # - comment_references + - constant_identifier_names + - control_flow_in_finally + - curly_braces_in_flow_control_structures + # - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + # - exhaustive_cases # required SDK 2.9.0 + - file_names + - flutter_style_todos + - hash_and_equals + - implementation_imports + - join_return_with_assignment + - library_names + - library_prefixes + # - lines_longer_than_80_chars + - no_adjacent_strings_in_list + - no_duplicate_case_values + - non_constant_identifier_names + - null_closures + - only_throw_errors + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_initializing_formals + - prefer_int_literals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_iterable_whereType + - prefer_mixin + - prefer_single_quotes + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - public_member_api_docs + - recursive_getters + - slash_for_doc_comments + - sort_constructors_first + # - sort_pub_dependencies + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - type_init_formals + - unawaited_futures + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_this + - unrelated_type_equality_checks + - use_function_type_syntax_for_parameters + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_string_buffers + - use_to_and_as_if_applicable + - valid_regexps + - void_checks diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml deleted file mode 100644 index f55005fb0..000000000 --- a/example/analysis_options.yaml +++ /dev/null @@ -1,4 +0,0 @@ -include: package:deriv_lint/analysis_options.yaml -analyzer: - exclude: - - test diff --git a/example/android/.gitignore b/example/android/.gitignore index bc2100d8f..55afd919c 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -5,3 +5,9 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 9625e105d..7c5696400 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index 88359b225..279576f38 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index d910cd48d..e822f10ba 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,15 +1,11 @@ PODS: - - connectivity (0.0.1): + - connectivity_plus (0.0.1): - Flutter - - Reachability - - device_info (0.0.1): + - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) - - flutter_deriv_api (0.0.1): + - package_info_plus (0.4.5): - Flutter - - package_info (0.0.1): - - Flutter - - Reachability (3.2) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -17,44 +13,35 @@ PODS: - Flutter DEPENDENCIES: - - connectivity (from `.symlinks/plugins/connectivity/ios`) - - device_info (from `.symlinks/plugins/device_info/ios`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - - flutter_deriv_api (from `.symlinks/plugins/flutter_deriv_api/ios`) - - package_info (from `.symlinks/plugins/package_info/ios`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - vibration (from `.symlinks/plugins/vibration/ios`) -SPEC REPOS: - trunk: - - Reachability - EXTERNAL SOURCES: - connectivity: - :path: ".symlinks/plugins/connectivity/ios" - device_info: - :path: ".symlinks/plugins/device_info/ios" + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/ios" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" Flutter: :path: Flutter - flutter_deriv_api: - :path: ".symlinks/plugins/flutter_deriv_api/ios" - package_info: - :path: ".symlinks/plugins/package_info/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/ios" + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" vibration: :path: ".symlinks/plugins/vibration/ios" SPEC CHECKSUMS: - connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 - device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - flutter_deriv_api: 9e29abd7cc5091b72303f9c8be549618415f1437 - package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 - Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 - shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 - vibration: 7d883d141656a1c1a6d8d238616b2042a51a1241 + connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + vibration: 8e2f50fc35bb736f9eecb7dd9f7047fbb6a6e888 -PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 +PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 -COCOAPODS: 1.11.3 +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 9cf427d85..2017cbecf 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -155,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -221,6 +221,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -341,7 +342,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -364,7 +365,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.silverhairs.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.chart_attempt; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -419,7 +420,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -468,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -493,7 +494,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.silverhairs.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.chart_attempt; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -516,7 +517,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.silverhairs.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.chart_attempt; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6e1..e67b2808a 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ true; + } +} + void main() { WidgetsFlutterBinding.ensureInitialized(); + HttpOverrides.global = MyHttpOverrides(); runApp(const MyApp()); } @@ -88,8 +96,6 @@ class _FullscreenChartState extends State { bool _waitingForHistory = false; - MarketChangeReminder? _marketsChangeReminder; - // Is used to make sure we make only one request to the API at a time. // We will not make a new call until the prev call has completed. late Completer _requestCompleter; @@ -178,49 +184,16 @@ class _FullscreenChartState extends State { resume: true, ); } - - await _setupMarketChangeReminder(); }); } - Future _setupMarketChangeReminder() async { - _marketsChangeReminder?.reset(); - _marketsChangeReminder = MarketChangeReminder( - () async => (await TradingTimesResponse.fetchTradingTimes( - const TradingTimesRequest(tradingTimes: 'today'), - )) - .tradingTimes!, - onMarketsStatusChange: (Map? statusChanges) { - if (statusChanges == null) { - return; - } - - for (int i = 0; i < _activeSymbols.length; i++) { - if (statusChanges[_activeSymbols[i].symbol] != null) { - _activeSymbols[i] = _activeSymbols[i].copyWith( - exchangeIsOpen: statusChanges[_activeSymbols[i].symbol], - ); - } - } - - _fillMarketSelectorList(); - - if (statusChanges[_symbol.name] != null) { - _symbol = _symbol.copyWith(isOpen: statusChanges[_symbol.name]); - - // Request for tick stream if symbol is changing from closed to open. - if (statusChanges[_symbol.name]!) { - _onIntervalSelected(granularity); - } - } - }, - ); - } - Future _getActiveSymbols() async { _activeSymbols = (await ActiveSymbolsResponse.fetchActiveSymbols( const ActiveSymbolsRequest( - activeSymbols: 'brief', productType: 'basic', landingCompany: null), + activeSymbols: 'brief', + productType: 'basic', + landingCompany: null, + ), )) .activeSymbols!; @@ -394,11 +367,7 @@ class _FullscreenChartState extends State { padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( children: [ - Expanded( - child: _markets == null - ? const SizedBox.shrink() - : _buildMarketSelectorButton(), - ), + Expanded(child: _buildMarketSelectorButton()), _buildChartTypeButton(), _buildIntervalSelector(), ], @@ -447,8 +416,6 @@ class _FullscreenChartState extends State { (_connectionBloc.state is connection_bloc.ConnectionConnectedState), opacity: _symbol.isOpen ? 1.0 : 0.5, - onCrosshairAppeared: () => - Vibration.vibrate(duration: 50), onVisibleAreaChanged: (int leftEpoch, int rightEpoch) { if (!_waitingForHistory && ticks.isNotEmpty && @@ -721,7 +688,6 @@ class _FullscreenChartState extends State { color: Colors.white, ), onPressed: () { - Vibration.vibrate(duration: 50); setState(() { switch (style) { case ChartStyle.ohlc: diff --git a/example/lib/settings_page.dart b/example/lib/settings_page.dart index e996fbf0a..448681e2f 100644 --- a/example/lib/settings_page.dart +++ b/example/lib/settings_page.dart @@ -31,10 +31,13 @@ class _SettingsPageState extends State { bool _hasChanged = false; @override - Widget build(BuildContext context) => WillPopScope( - onWillPop: () async { + Widget build(BuildContext context) => PopScope( + canPop: false, + onPopInvoked: (bool didPop) { + if (didPop) { + return; + } Navigator.of(context).pop(_hasChanged); - return false; }, child: Scaffold( appBar: AppBar(title: const Text('Setting')), diff --git a/example/lib/utils/market_change_reminder.dart b/example/lib/utils/market_change_reminder.dart deleted file mode 100644 index 0af465b17..000000000 --- a/example/lib/utils/market_change_reminder.dart +++ /dev/null @@ -1,160 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -import 'package:flutter_deriv_api/api/response/trading_times_response_result.dart'; -import 'package:intl/intl.dart'; - -/// Markets status change callback. (List of symbols that have been changed.) -typedef OnMarketsStatusChange = void Function(Map? symbols); - -/// A class to remind when there is a change on market. -/// -/// -/// This class will notify about the status changes in market by calling -/// [onMarketsStatusChange] with a Map of {symbolCode: status}. -/// For example it might be like: -/// -/// {'frxAUDJPY': false, 'frxUSDMXN': true} -/// Meaning that at this time frxAUDJPY will become closed and frxUSDMXN opens. -/// -/// -/// At start this class gets the TradingTimes and it will setup a queue of the -/// upcoming market changes and sets a timer to notify about the first one. -/// Once first timer finishes it will set for the next status change and so on, -/// until its queue becomes empty and will sets the last timer to the start of -/// tomorrow to reset its queue. -class MarketChangeReminder { - /// Initializes a class to remind when there is a change on market. - MarketChangeReminder( - this.onTradingTimes, { - Future Function()? onCurrentTime, - this.onMarketsStatusChange, - }) : _onCurrentTime = onCurrentTime ?? (() async => DateTime.now()) { - _init(); - } - - static final DateFormat _dateFormat = DateFormat('hh:mm:ss'); - static final RegExp _timeFormatReg = RegExp(r'[0-9]{2}:[0-9]{2}:[0-9]{2}'); - - /// Callback to get server time - /// - /// If not set it will be using DateTime.now().toUTC(); - final Future Function() _onCurrentTime; - - /// Callback to get trading times of today - final Future Function() onTradingTimes; - - // TODO(Ramin): Consider using a reliable timer if Dart's version had - // any problems. - Timer? _reminderTimer; - - /// Gets called when market status changes with the list of symbols that their - /// open/closed status is changed. - final OnMarketsStatusChange? onMarketsStatusChange; - - /// List of upcoming market change times. - /// - /// [SplayTreeMap] has been used to have the change times in correct order - /// while we are adding new entries to it. - final SplayTreeMap> statusChangeTimes = - SplayTreeMap>(); - - Future _init() async { - await _fillStatusChangeMap(); - await _setReminderTimer(); - } - - Future _fillStatusChangeMap() async { - final DateTime now = await _onCurrentTime(); - - final TradingTimes todayTradingTimes = await onTradingTimes(); - - for (final MarketsItem? market in todayTradingTimes.markets) { - for (final SubmarketsItem? subMarket in market!.submarkets!) { - for (final SymbolsItem? symbol in subMarket!.symbols!) { - final List openTimes = symbol!.times!['open']; - final List? closeTimes = symbol.times!['close']; - - final bool isOpenAllDay = openTimes.length == 1 && - openTimes[0] == '00:00:00' && - closeTimes![0] == '23:59:59'; - final bool isClosedAllDay = openTimes.length == 1 && - closeTimes![0] == '--' && - closeTimes[0] == '--'; - - if (isOpenAllDay || isClosedAllDay) { - continue; - } - - for (final String? time in openTimes) { - _addEntryToStatusChanges(time!, symbol.symbol, true, now); - } - - for (final String? time in closeTimes as List) { - _addEntryToStatusChanges(time!, symbol.symbol, false, now); - } - } - } - } - } - - void _addEntryToStatusChanges( - String time, - String? symbol, - bool goesOpen, - DateTime now, - ) { - if (_timeFormatReg.allMatches(time).length != 1) { - return; - } - - final DateTime hourMinSec = _dateFormat.parse(time); - final DateTime statusChangeTime = DateTime.utc( - now.year, - now.month, - now.day, - hourMinSec.hour, - hourMinSec.minute, - hourMinSec.second, - ).add(const Duration(seconds: 5)); - - if (now.isAfter(statusChangeTime)) { - return; - } - - statusChangeTimes[statusChangeTime] ??= {}; - - statusChangeTimes[statusChangeTime]![symbol] = goesOpen; - } - - /// Removes the next upcoming market change time from [statusChangeTimes] and - /// sets a timer for it. - Future _setReminderTimer() async { - _reminderTimer?.cancel(); - - final DateTime now = await _onCurrentTime(); - - if (statusChangeTimes.isNotEmpty) { - final DateTime nextStatusChangeTime = statusChangeTimes.firstKey()!; - final Map? symbolsChanging = - statusChangeTimes.remove(nextStatusChangeTime); - - _reminderTimer = Timer( - nextStatusChangeTime.difference(now), - () { - onMarketsStatusChange?.call(symbolsChanging); - - // Reminder for the next status change in the Queue. - _setReminderTimer(); - }, - ); - } else { - // Setting a timer to reset trading times when next day start - final DateTime tomorrowStart = DateTime(now.year, now.month, now.day + 1); - _reminderTimer = Timer(tomorrowStart.difference(now), () => _init()); - } - } - - /// Cancels current reminder timer. - void reset() => _reminderTimer?.cancel(); -} diff --git a/example/test/market_change_reminder_test.dart b/example/test/market_change_reminder_test.dart deleted file mode 100644 index c206fa952..000000000 --- a/example/test/market_change_reminder_test.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'dart:convert'; -import 'dart:async'; - -import 'package:example/utils/market_change_reminder.dart'; -import 'package:flutter_deriv_api/api/common/trading/trading_times.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'trading_times_mock_data.dart'; - -void main() { - group('Trading times', () { - test('Trading times reminder queue fills in the correct order', () async { - MarketChangeReminder tradingTimesReminder = MarketChangeReminder( - () => Future.value(TradingTimes.fromJson( - jsonDecode(tradingTimesResponse)['trading_times'], - )), - onCurrentTime: () => - Future.value(DateTime.utc(2020, 10, 10, 4, 0, 0)), - ); - - await Future.delayed(const Duration(milliseconds: 1)); - - final firstMarketChangeDate = - tradingTimesReminder.statusChangeTimes.firstKey(); - - final firstMarkChangeSymbols = - tradingTimesReminder.statusChangeTimes[firstMarketChangeDate!]!; - - // First date will be remove to set the first reminding timer - expect(tradingTimesReminder.statusChangeTimes.length, 3); - expect(firstMarketChangeDate, DateTime(2020, 10, 10, 7, 30)); - expect(firstMarkChangeSymbols.keys.first, 'OTC_AS51'); - expect(firstMarkChangeSymbols.values.first, true); - - final lastMarketChangeDate = - tradingTimesReminder.statusChangeTimes.lastKey(); - - final lastMarkChangeSymbols = - tradingTimesReminder.statusChangeTimes[lastMarketChangeDate!]!; - - expect(lastMarketChangeDate, DateTime(2020, 10, 10, 22)); - expect(lastMarkChangeSymbols.keys.first, 'OTC_HSI'); - expect(lastMarkChangeSymbols.values.first, false); - }); - }); -} diff --git a/example/test/trading_times_mock_data.dart b/example/test/trading_times_mock_data.dart deleted file mode 100644 index b92d8c343..000000000 --- a/example/test/trading_times_mock_data.dart +++ /dev/null @@ -1,45 +0,0 @@ -const String tradingTimesResponse = '''{ - "msg_type": "trading_times", - "trading_times": { - "markets": [ - { - "name": "Stock Indices", - "submarkets": [ - { - "name": "Asia/Oceania", - "symbols": [ - { - "name": "Australian Index", - "symbol": "OTC_AS51", - "times": { - "open": [ - "00:00:00", - "07:30:00" - ], - "close": [ - "06:30:00", - "20:00:00" - ] - } - }, - { - "name": "Hong Kong Index", - "symbol": "OTC_HSI", - "times": { - "open": [ - "01:30:00", - "07:30:00" - ], - "close": [ - "04:00:00", - "22:00:00" - ] - } - } - ] - } - ] - } - ] - } -}'''; From 3f731002ba631ec169df56c57b298eebf706fbe2 Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Tue, 30 Sep 2025 18:24:02 +0800 Subject: [PATCH 07/14] feat: improve chart auto-scrolling for inconsistent feeds - Add continuous frame updates via Ticker in XAxisWeb for proper auto-scrolling - Enhance _followCurrentTick logic to handle inconsistent feeds - Add _shouldFollowLastTickForInconsistentFeed for better feed handling - Improve onNewFrame method to scroll to last tick position when needed - Add proper ticker disposal in XAxisWeb dispose method --- .../chart/x_axis/widgets/x_axis_web.dart | 6 ++++ .../chart/x_axis/x_axis_model.dart | 28 +++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/src/deriv_chart/chart/x_axis/widgets/x_axis_web.dart b/lib/src/deriv_chart/chart/x_axis/widgets/x_axis_web.dart index 5e1010f83..d8339a818 100644 --- a/lib/src/deriv_chart/chart/x_axis/widgets/x_axis_web.dart +++ b/lib/src/deriv_chart/chart/x_axis/widgets/x_axis_web.dart @@ -1,6 +1,7 @@ import 'package:deriv_chart/src/deriv_chart/chart/x_axis/widgets/x_axis_base.dart'; import 'package:deriv_chart/src/models/chart_config.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:provider/provider.dart'; /// A class representing the X-axis for a web-based chart. @@ -32,11 +33,15 @@ class XAxisWeb extends XAxisBase { class _XAxisStateWeb extends XAxisState { AnimationController? _scrollAnimationController; + Ticker? _ticker; @override void initState() { super.initState(); + // Add continuous frame updates for proper auto-scrolling + _ticker = createTicker(model.onNewFrame)..start(); + _scrollAnimationController = AnimationController( vsync: this, duration: widget.scrollAnimationDuration, @@ -85,6 +90,7 @@ class _XAxisStateWeb extends XAxisState { @override void dispose() { + _ticker?.dispose(); _scrollAnimationController?.dispose(); super.dispose(); diff --git a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart index 1e343051d..37bcddbaf 100644 --- a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart +++ b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart @@ -179,16 +179,28 @@ class XAxisModel extends ChangeNotifier { rightBoundEpoch == _maxRightBoundEpoch || rightBoundEpoch == _minRightBoundEpoch; + // [AI] bool get _followCurrentTick => _autoPanEnabled && isLive && - rightBoundEpoch > _nowEpoch && + (rightBoundEpoch > _nowEpoch || + _shouldFollowLastTickForInconsistentFeed) && _currentTickFarEnoughFromLeftBound; bool get _currentTickFarEnoughFromLeftBound => _entries!.isEmpty || _entries!.last.epoch > _shiftEpoch(leftBoundEpoch, autoPanOffset); + /// For inconsistent feeds, we should follow the last tick instead of current time + /// since there can be long gaps between ticks and current time + bool get _shouldFollowLastTickForInconsistentFeed => + _autoPanEnabled && + isLive && + (_entries?.isNotEmpty ?? false) && + rightBoundEpoch < + _shiftEpoch(_entries!.last.epoch, _maxCurrentTickOffset); + // [/AI] + /// Current scale value. double get msPerPx => _msPerPx; @@ -206,6 +218,7 @@ class XAxisModel extends ChangeNotifier { /// Check [_currentViewingMode]. bool get dataFitEnabled => _dataFitMode; + // [AI] /// Current mode that controls chart's zooming and scrolling behaviour. ViewingMode get _currentViewingMode { if (_panSpeed != 0) { @@ -219,6 +232,7 @@ class XAxisModel extends ChangeNotifier { } return ViewingMode.stationary; } + // [/AI] /// Called on each tick's curve animation /// Updates scroll position if the [_currentViewingMode] in follow mode. @@ -232,6 +246,7 @@ class XAxisModel extends ChangeNotifier { } } + // [AI] /// Called on each frame. /// Updates zoom and scroll position based on current [_currentViewingMode]. void onNewFrame(Duration _) { @@ -243,7 +258,15 @@ class XAxisModel extends ChangeNotifier { // TODO(NA): Consider refactoring the switch with OOP pattern. https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html switch (_currentViewingMode) { case ViewingMode.followCurrentTick: - _scrollTo(_rightBoundEpoch + elapsedMs); + // For inconsistent feeds, scroll to the last tick position instead of current time + if (_shouldFollowLastTickForInconsistentFeed && + (_entries?.isNotEmpty ?? false)) { + final int targetEpoch = + _shiftEpoch(_entries!.last.epoch, _maxCurrentTickOffset); + _scrollTo(targetEpoch); + } else { + _scrollTo(_rightBoundEpoch + elapsedMs); + } break; case ViewingMode.fitData: fitAvailableData(); @@ -257,6 +280,7 @@ class XAxisModel extends ChangeNotifier { _lastEpoch = newNowTime; } + // [/AI] /// Updates scrolling bounds and time gaps based on the main chart's entries. /// From 2d6eae850255f44ad3fd5da9144cf9c116d489f5 Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Tue, 30 Sep 2025 18:26:09 +0800 Subject: [PATCH 08/14] chore: remove comments --- lib/src/deriv_chart/chart/x_axis/x_axis_model.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart index 37bcddbaf..cbf0f0ff5 100644 --- a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart +++ b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart @@ -179,7 +179,7 @@ class XAxisModel extends ChangeNotifier { rightBoundEpoch == _maxRightBoundEpoch || rightBoundEpoch == _minRightBoundEpoch; - // [AI] + // [AI] bool get _followCurrentTick => _autoPanEnabled && isLive && @@ -218,7 +218,7 @@ class XAxisModel extends ChangeNotifier { /// Check [_currentViewingMode]. bool get dataFitEnabled => _dataFitMode; - // [AI] + // [AI] /// Current mode that controls chart's zooming and scrolling behaviour. ViewingMode get _currentViewingMode { if (_panSpeed != 0) { @@ -246,7 +246,7 @@ class XAxisModel extends ChangeNotifier { } } - // [AI] + // [AI] /// Called on each frame. /// Updates zoom and scroll position based on current [_currentViewingMode]. void onNewFrame(Duration _) { From 542c5d8ad85fc5d8cac283f1be8527558321d0a5 Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Tue, 30 Sep 2025 18:28:05 +0800 Subject: [PATCH 09/14] chore: remove comments --- lib/src/deriv_chart/chart/x_axis/x_axis_model.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart index cbf0f0ff5..be51013ac 100644 --- a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart +++ b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart @@ -199,7 +199,7 @@ class XAxisModel extends ChangeNotifier { (_entries?.isNotEmpty ?? false) && rightBoundEpoch < _shiftEpoch(_entries!.last.epoch, _maxCurrentTickOffset); - // [/AI] + // [/AI] /// Current scale value. double get msPerPx => _msPerPx; @@ -232,7 +232,7 @@ class XAxisModel extends ChangeNotifier { } return ViewingMode.stationary; } - // [/AI] + // [/AI] /// Called on each tick's curve animation /// Updates scroll position if the [_currentViewingMode] in follow mode. @@ -280,7 +280,7 @@ class XAxisModel extends ChangeNotifier { _lastEpoch = newNowTime; } - // [/AI] + // [/AI] /// Updates scrolling bounds and time gaps based on the main chart's entries. /// From 59113acdc850f62f21dbd1f16be18253a53ebeaa Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Tue, 30 Sep 2025 18:29:22 +0800 Subject: [PATCH 10/14] chore: remove comments --- lib/src/deriv_chart/chart/x_axis/x_axis_model.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart index be51013ac..5b8ceb167 100644 --- a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart +++ b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart @@ -199,7 +199,6 @@ class XAxisModel extends ChangeNotifier { (_entries?.isNotEmpty ?? false) && rightBoundEpoch < _shiftEpoch(_entries!.last.epoch, _maxCurrentTickOffset); - // [/AI] /// Current scale value. double get msPerPx => _msPerPx; @@ -232,7 +231,6 @@ class XAxisModel extends ChangeNotifier { } return ViewingMode.stationary; } - // [/AI] /// Called on each tick's curve animation /// Updates scroll position if the [_currentViewingMode] in follow mode. @@ -280,7 +278,6 @@ class XAxisModel extends ChangeNotifier { _lastEpoch = newNowTime; } - // [/AI] /// Updates scrolling bounds and time gaps based on the main chart's entries. /// From b9bc1114a3b7c63e7126b9a9376d39448889b7a6 Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Tue, 30 Sep 2025 18:38:21 +0800 Subject: [PATCH 11/14] chore: remove comments --- lib/src/deriv_chart/chart/x_axis/x_axis_model.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart index 5b8ceb167..b1e476100 100644 --- a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart +++ b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart @@ -179,7 +179,6 @@ class XAxisModel extends ChangeNotifier { rightBoundEpoch == _maxRightBoundEpoch || rightBoundEpoch == _minRightBoundEpoch; - // [AI] bool get _followCurrentTick => _autoPanEnabled && isLive && @@ -217,7 +216,6 @@ class XAxisModel extends ChangeNotifier { /// Check [_currentViewingMode]. bool get dataFitEnabled => _dataFitMode; - // [AI] /// Current mode that controls chart's zooming and scrolling behaviour. ViewingMode get _currentViewingMode { if (_panSpeed != 0) { @@ -244,7 +242,6 @@ class XAxisModel extends ChangeNotifier { } } - // [AI] /// Called on each frame. /// Updates zoom and scroll position based on current [_currentViewingMode]. void onNewFrame(Duration _) { From 172f71ceb83d63868de920e9163fbebb90996531 Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Wed, 1 Oct 2025 14:24:29 +0800 Subject: [PATCH 12/14] fix: allow left scrolling to view historical tick data - Add user interaction tracking to prevent auto-scroll interference - Modify _shouldFollowLastTickForInconsistentFeed to respect manual scrolling - Auto-scroll only re-enables when user scrolls back to latest tick area - Preserve existing auto-follow behavior for live data feeds - Fix issue where users couldn't scroll left to view tick history Resolves scrolling limitation that prevented viewing historical data --- .../chart/x_axis/widgets/x_axis_web.dart | 38 ++++++++- .../chart/x_axis/x_axis_model.dart | 84 ++++++++++++++++--- 2 files changed, 107 insertions(+), 15 deletions(-) diff --git a/lib/src/deriv_chart/chart/x_axis/widgets/x_axis_web.dart b/lib/src/deriv_chart/chart/x_axis/widgets/x_axis_web.dart index d8339a818..859f360be 100644 --- a/lib/src/deriv_chart/chart/x_axis/widgets/x_axis_web.dart +++ b/lib/src/deriv_chart/chart/x_axis/widgets/x_axis_web.dart @@ -39,9 +39,6 @@ class _XAxisStateWeb extends XAxisState { void initState() { super.initState(); - // Add continuous frame updates for proper auto-scrolling - _ticker = createTicker(model.onNewFrame)..start(); - _scrollAnimationController = AnimationController( vsync: this, duration: widget.scrollAnimationDuration, @@ -69,9 +66,41 @@ class _XAxisStateWeb extends XAxisState { }, ); + // Add listener for auto-scrolling state changes + model.addListener(_onModelChanged); + + // Start ticker only if auto-scrolling is needed + _startAutoScrollTicker(); + fitData(); } + void _onModelChanged() { + // Check if we need to start/stop the ticker based on auto-scrolling state + final bool shouldRunTicker = _shouldRunTicker(); + + if (shouldRunTicker && _ticker == null) { + _startAutoScrollTicker(); + } else if (!shouldRunTicker && _ticker != null) { + _stopAutoScrollTicker(); + } + } + + bool _shouldRunTicker() { + // Ticker should run when auto-scrolling is active + return model.isLive && (model.dataFitEnabled || model.isAutoPanEnabled); + } + + void _startAutoScrollTicker() { + _ticker ??= createTicker(model.onNewFrame)..start(); + } + + void _stopAutoScrollTicker() { + _ticker?.stop(); + _ticker?.dispose(); + _ticker = null; + } + @override void didUpdateWidget(XAxisBase oldWidget) { super.didUpdateWidget(oldWidget); @@ -90,7 +119,8 @@ class _XAxisStateWeb extends XAxisState { @override void dispose() { - _ticker?.dispose(); + model.removeListener(_onModelChanged); + _stopAutoScrollTicker(); _scrollAnimationController?.dispose(); super.dispose(); diff --git a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart index b1e476100..f7061b3a0 100644 --- a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart +++ b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart @@ -143,6 +143,7 @@ class XAxisModel extends ChangeNotifier { late AnimationController _scrollAnimationController; double? _prevScrollAnimationValue; bool _autoPanEnabled = true; + bool _userInteracting = false; late bool _dataFitMode; double _msPerPx = 1000; double? _prevMsPerPx; @@ -192,8 +193,10 @@ class XAxisModel extends ChangeNotifier { /// For inconsistent feeds, we should follow the last tick instead of current time /// since there can be long gaps between ticks and current time + /// But only if the user is not manually interacting with the chart bool get _shouldFollowLastTickForInconsistentFeed => _autoPanEnabled && + !_userInteracting && isLive && (_entries?.isNotEmpty ?? false) && rightBoundEpoch < @@ -216,6 +219,10 @@ class XAxisModel extends ChangeNotifier { /// Check [_currentViewingMode]. bool get dataFitEnabled => _dataFitMode; + /// Whether auto-panning is enabled. + /// Used to determine if ticker should be running for auto-scrolling. + bool get isAutoPanEnabled => _autoPanEnabled; + /// Current mode that controls chart's zooming and scrolling behaviour. ViewingMode get _currentViewingMode { if (_panSpeed != 0) { @@ -382,18 +389,23 @@ class XAxisModel extends ChangeNotifier { /// Enables data fit viewing mode. void enableDataFit() { - _dataFitMode = true; - if (kIsWeb) { - fitAvailableData(); + if (_dataFitMode != true) { + _dataFitMode = true; + // Reset user interaction when entering data fit mode + _userInteracting = false; + if (kIsWeb) { + fitAvailableData(); + } + notifyListeners(); } - - notifyListeners(); } /// Disables data fit viewing mode. void disableDataFit() { - _dataFitMode = false; - notifyListeners(); + if (_dataFitMode != false) { + _dataFitMode = false; + notifyListeners(); + } } /// Sets [panSpeed] if input not null, otherwise sets to `0`. @@ -402,15 +414,46 @@ class XAxisModel extends ChangeNotifier { /// Enables autopanning when current tick is visible. void enableAutoPan() { - _autoPanEnabled = true; - notifyListeners(); + if (_autoPanEnabled != true) { + _autoPanEnabled = true; + notifyListeners(); + } } /// Disables autopanning when current tick is visible. /// E.g. crosshair disables autopan while it is visible. void disableAutoPan() { - _autoPanEnabled = false; - notifyListeners(); + if (_autoPanEnabled != false) { + _autoPanEnabled = false; + notifyListeners(); + } + } + + /// Resets the user interaction flag to allow auto-pan to resume. + /// This can be called when you want to programmatically re-enable auto-following. + void resetUserInteraction() { + _userInteracting = false; + } + + /// Checks if the user has scrolled back to the latest tick area + /// and re-enables auto-scrolling if they have + void _checkIfUserScrolledToLatestTick() { + if (_userInteracting && isLive && (_entries?.isNotEmpty ?? false)) { + // Calculate the target position where auto-scroll should resume + final int latestTickWithOffset = + _shiftEpoch(_entries!.last.epoch, _maxCurrentTickOffset); + + // If the user has scrolled to within a reasonable distance of the latest tick, + // re-enable auto-scrolling + final int distanceFromLatest = + (rightBoundEpoch - latestTickWithOffset).abs(); + final int threshold = + (_maxCurrentTickOffset * 0.5).round(); // 50% of max offset + + if (distanceFromLatest <= threshold) { + _userInteracting = false; + } + } } /// Convert ms to px using current scale. @@ -454,6 +497,9 @@ class XAxisModel extends ChangeNotifier { _scrollAnimationController.stop(); _prevMsPerPx = _msPerPx; + // Mark that user is interacting to prevent auto-scroll interference + _userInteracting = true; + // Exit data fit mode. disableDataFit(); } @@ -470,6 +516,8 @@ class XAxisModel extends ChangeNotifier { /// Called when user is panning the chart. void onPanUpdate(DragUpdateDetails details) { if (!_isScrollBlocked) { + // Mark that user is interacting to prevent auto-scroll interference + _userInteracting = true; scrollBy(-details.delta.dx); } } @@ -490,8 +538,19 @@ class XAxisModel extends ChangeNotifier { /// Called to scroll the chart void scrollBy(double pxShift) { + // If this is a significant manual scroll (not just tiny adjustments), + // mark as user interaction to prevent auto-scroll interference + if (pxShift.abs() > 1.0) { + _userInteracting = true; + } + _rightBoundEpoch = _shiftEpoch(_rightBoundEpoch, pxShift); _clampRightBoundEpoch(); + + // Check if user has scrolled back to the latest tick area + // If so, re-enable auto-scrolling + _checkIfUserScrolledToLatestTick(); + onScroll?.call(); notifyListeners(); } @@ -522,6 +581,9 @@ class XAxisModel extends ChangeNotifier { /// Animate scrolling to current tick. void scrollToLastTick({bool animate = true}) { + // Re-enable auto-pan when user explicitly scrolls to last tick + _userInteracting = false; + final Duration duration = animate ? const Duration(milliseconds: 600) : Duration.zero; final int target = _shiftEpoch( From 055d89b4aff181c9475724f01cae0614bc336b8e Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Wed, 1 Oct 2025 15:06:05 +0800 Subject: [PATCH 13/14] refactor: extract magic number into named constant for scroll threshold - Add significantScrollThreshold constant (value: 1) to improve readability - Replace magic number 1.0 in scrollBy() method with named constant - Improves code maintainability and makes threshold purpose explicit --- lib/src/deriv_chart/chart/x_axis/x_axis_model.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart index f7061b3a0..ec223b793 100644 --- a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart +++ b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart @@ -20,6 +20,10 @@ const double autoPanOffset = 30; /// Padding around data used in data-fit mode. const EdgeInsets defaultDataFitPadding = EdgeInsets.only(left: 16, right: 120); +/// Minimum pixel threshold for scroll movements to be considered significant +/// user interaction (not just tiny automatic adjustments). +const double significantScrollThreshold = 1; + /// Modes that control chart's zoom and scroll behaviour without user /// interaction. enum ViewingMode { @@ -540,7 +544,7 @@ class XAxisModel extends ChangeNotifier { void scrollBy(double pxShift) { // If this is a significant manual scroll (not just tiny adjustments), // mark as user interaction to prevent auto-scroll interference - if (pxShift.abs() > 1.0) { + if (pxShift.abs() > significantScrollThreshold) { _userInteracting = true; } From b7c729eac406f39dc464e01d93ddb4a4dcd00376 Mon Sep 17 00:00:00 2001 From: Jim Daniels Wasswa Date: Wed, 1 Oct 2025 15:52:18 +0800 Subject: [PATCH 14/14] fix: add missing notifyListeners() call in resetUserInteraction() - Ensures UI elements are notified when user interaction state changes - Fixes inconsistency where other methods setting _userInteracting call notifyListeners() - Improves responsiveness when programmatically resetting auto-pan behavior --- lib/src/deriv_chart/chart/x_axis/x_axis_model.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart index ec223b793..c5d311cf1 100644 --- a/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart +++ b/lib/src/deriv_chart/chart/x_axis/x_axis_model.dart @@ -437,6 +437,7 @@ class XAxisModel extends ChangeNotifier { /// This can be called when you want to programmatically re-enable auto-following. void resetUserInteraction() { _userInteracting = false; + notifyListeners(); } /// Checks if the user has scrolled back to the latest tick area