From bc8e2f71a3f8fc8c392effc72eef2808428bf76e Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Tue, 25 Aug 2020 10:45:18 +0800 Subject: [PATCH 01/21] add update_checker package --- .gitignore | 76 +++ packages/update_checker/CHANGELOG.md | 3 + packages/update_checker/LICENSE | 1 + packages/update_checker/README.md | 14 + .../lib/flutter_update_plugin.dart | 2 + packages/update_checker/lib/update_bloc.dart | 39 ++ packages/update_checker/lib/update_event.dart | 34 ++ packages/update_checker/lib/update_info.dart | 13 + packages/update_checker/lib/update_state.dart | 42 ++ .../lib/utils/firebase_utils.dart | 68 +++ packages/update_checker/lib/utils/misc.dart | 14 + .../lib/widgets/update_check.dart | 38 ++ packages/update_checker/pubspec.lock | 523 ++++++++++++++++++ packages/update_checker/pubspec.yaml | 24 + .../test/flutter_update_plugin_test.dart | 66 +++ 15 files changed, 957 insertions(+) create mode 100644 .gitignore create mode 100644 packages/update_checker/CHANGELOG.md create mode 100644 packages/update_checker/LICENSE create mode 100644 packages/update_checker/README.md create mode 100644 packages/update_checker/lib/flutter_update_plugin.dart create mode 100644 packages/update_checker/lib/update_bloc.dart create mode 100644 packages/update_checker/lib/update_event.dart create mode 100644 packages/update_checker/lib/update_info.dart create mode 100644 packages/update_checker/lib/update_state.dart create mode 100644 packages/update_checker/lib/utils/firebase_utils.dart create mode 100644 packages/update_checker/lib/utils/misc.dart create mode 100644 packages/update_checker/lib/widgets/update_check.dart create mode 100644 packages/update_checker/pubspec.lock create mode 100644 packages/update_checker/pubspec.yaml create mode 100644 packages/update_checker/test/flutter_update_plugin_test.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..016dc1b66 --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ +.DS_Store +.atom/ +.idea/ +.vscode/* +!.vscode/tasks.json +!.vscode/settings.json + +.packages +.pub/ +.dart_tool/ +flutter_export_environment.sh + +examples/all_plugins/pubspec.yaml + +Podfile.lock +Pods/ +Podfile +.symlinks/ +**/Flutter/ephemeral/ +**/Flutter/Flutter.podspec +**/Flutter/App.framework/ +**/Flutter/Flutter.framework/ +**/Flutter/Generated.xcconfig +**/Flutter/flutter_assets/ +ServiceDefinitions.json +xcuserdata/ + +local.properties +keystore.properties +.gradle/ +gradlew +gradlew.bat +gradle-wrapper.jar +*.iml + +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m +GeneratedPluginRegistrant.java +GeneratedPluginRegistrant.swift +generated_plugin_registrant.dart + +build/ +.flutter-plugins +.flutter-plugins-dependencies + +.project +.classpath +.settings +/pubspec.yaml +.last_build_id + +# Docs + +# Dependencies +node_modules + +# Production +website/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +firebase-debug.log +firestore-debug.log +ui-debug.log \ No newline at end of file diff --git a/packages/update_checker/CHANGELOG.md b/packages/update_checker/CHANGELOG.md new file mode 100644 index 000000000..ac071598e --- /dev/null +++ b/packages/update_checker/CHANGELOG.md @@ -0,0 +1,3 @@ +## [0.0.1] - TODO: Add release date. + +* TODO: Describe initial release. diff --git a/packages/update_checker/LICENSE b/packages/update_checker/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/packages/update_checker/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/update_checker/README.md b/packages/update_checker/README.md new file mode 100644 index 000000000..14758cc3a --- /dev/null +++ b/packages/update_checker/README.md @@ -0,0 +1,14 @@ +# update_checker + +A new Flutter package project. + +## Getting Started + +This project is a starting point for a Dart +[package](https://flutter.dev/developing-packages/), +a library module containing code that can be shared easily across +multiple Flutter or Dart projects. + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/update_checker/lib/flutter_update_plugin.dart b/packages/update_checker/lib/flutter_update_plugin.dart new file mode 100644 index 000000000..89f581434 --- /dev/null +++ b/packages/update_checker/lib/flutter_update_plugin.dart @@ -0,0 +1,2 @@ +library flutter_update_plugin; + diff --git a/packages/update_checker/lib/update_bloc.dart b/packages/update_checker/lib/update_bloc.dart new file mode 100644 index 000000000..14050b4f2 --- /dev/null +++ b/packages/update_checker/lib/update_bloc.dart @@ -0,0 +1,39 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:update_checker/update_event.dart'; +import 'package:update_checker/update_info.dart'; +import 'package:update_checker/update_state.dart'; +import 'package:update_checker/utils/firebase_utils.dart'; + +class UpdateCheckBloc extends Bloc { + bool _updateCancelled = false; + FirebaseUtils firebaseUtils; + + @override + UpdateCheckState get initialState => UpdateNotAvailable(); + + UpdateCheckBloc({FirebaseUtils fbUtils}) { + this.firebaseUtils = fbUtils ?? FirebaseUtils(); + this.add(UpdateCheckStart()); + } + + @override + Stream mapEventToState(UpdateCheckEvent event) async* { + if (event is UpdateCheckStart) { + _updateCancelled = false; + this.add(UpdateCheckInProgress()); + } else if (event is UpdateCheckInProgress) { + UpdateInfo info = await firebaseUtils.fetchBuildNumbers(); + if (info != null && _updateCancelled == false){ + yield UpdateAvailable(info); + } + else { + yield UpdateNotAvailable(); + } + } else if (event is UpdateCheckCancel) { + _updateCancelled = true; + yield UpdateCancelled(); + } else if (event is UpdateCheckMarkSeen) { + yield UpdateSeen(); + } + } +} diff --git a/packages/update_checker/lib/update_event.dart b/packages/update_checker/lib/update_event.dart new file mode 100644 index 000000000..3237585fa --- /dev/null +++ b/packages/update_checker/lib/update_event.dart @@ -0,0 +1,34 @@ +import 'package:equatable/equatable.dart'; + +abstract class UpdateCheckEvent extends Equatable { + @override + List get props => []; +} + +class UpdateCheckStart extends UpdateCheckEvent { + @override + String toString() { + return 'UpdateCheckEvent(UpdateCheckStart)'; + } +} + +class UpdateCheckInProgress extends UpdateCheckEvent { + @override + String toString() { + return 'UpdateCheckEvent(UpdateCheckInProgress)'; + } +} + +class UpdateCheckCancel extends UpdateCheckEvent { + @override + String toString() { + return 'UpdateCheckEvent(UpdateCheckCancel)'; + } +} + +class UpdateCheckMarkSeen extends UpdateCheckEvent { + @override + String toString() { + return 'UpdateCheckEvent(UpdateCheckMarkSeen)'; + } +} diff --git a/packages/update_checker/lib/update_info.dart b/packages/update_checker/lib/update_info.dart new file mode 100644 index 000000000..bfc2ad9d4 --- /dev/null +++ b/packages/update_checker/lib/update_info.dart @@ -0,0 +1,13 @@ +class UpdateInfo { + final int buildNumber; + final String changelog; + final String url; + final bool isOptional; + + UpdateInfo({ + this.buildNumber, + this.changelog, + this.url, + this.isOptional, + }); +} diff --git a/packages/update_checker/lib/update_state.dart b/packages/update_checker/lib/update_state.dart new file mode 100644 index 000000000..3d980177f --- /dev/null +++ b/packages/update_checker/lib/update_state.dart @@ -0,0 +1,42 @@ +import 'package:equatable/equatable.dart'; +import 'package:update_checker/update_info.dart'; + +abstract class UpdateCheckState extends Equatable { + @override + List get props => []; +} + +class UpdateNotAvailable extends UpdateCheckState { + @override + String toString() { + return 'UpdateNotAvailable'; + } +} + +class UpdateAvailable extends UpdateCheckState { + final UpdateInfo updateInfo; + + UpdateAvailable(this.updateInfo); + + @override + List get props => [updateInfo]; + + @override + String toString() { + return 'UpdateAvailable_${updateInfo.buildNumber}_${updateInfo.isOptional}'; + } +} + +class UpdateCancelled extends UpdateCheckState { + @override + String toString() { + return 'UpdateCheckState(UpdateCancelled)'; + } +} + +class UpdateSeen extends UpdateCheckState { + @override + String toString() { + return 'UpdateCheckState(UpdateSeen)'; + } +} diff --git a/packages/update_checker/lib/utils/firebase_utils.dart b/packages/update_checker/lib/utils/firebase_utils.dart new file mode 100644 index 000000000..a7d57a332 --- /dev/null +++ b/packages/update_checker/lib/utils/firebase_utils.dart @@ -0,0 +1,68 @@ +import 'dart:io'; +import 'dart:developer' as dev; +import 'package:firebase_database/firebase_database.dart'; +import 'package:update_checker/update_info.dart'; +import 'package:update_checker/utils/misc.dart'; +import 'package:package_info/package_info.dart'; + +class FirebaseUtils { +/* + Firebase Database looks like as follows + build: + android: + option: + buildnumer: 24 + changelog: 'RFAyUCB2ZXJ....' // Base64 + url: 'https://alternative.app.link' + mandatory: + buildnumber: 18 + changelog: 'RFAyUCB2ZXJ....' // Base64 + url: 'https://alternative.app.link' + */ + Future fetchBuildNumbers() async { + try { + String platform = Platform.operatingSystem; + DatabaseReference dbRef = + FirebaseDatabase.instance.reference().child('build').child(platform); + + final buildRemoteInfo = (await dbRef.once()).value; + final buildNumberOptional = buildRemoteInfo['optional']['buildnumber']; + final buildNumberMandatory = buildRemoteInfo['mandatory']['buildnumber']; + + // Get BuildNumber currently running + final packageInfo = await PackageInfo.fromPlatform(); + final buildNumberCurrent = int.tryParse(packageInfo.buildNumber) ?? -1; + + if (buildNumberCurrent > 0 && buildNumberCurrent < buildNumberMandatory) { + // Current build is lower than Minimum Required Build + final String buildAppUrl = buildRemoteInfo['mandatory']['url'] ?? null; + final String changelog = + decodeBase64(buildRemoteInfo['mandatory']['changelog'] ?? ''); + + return UpdateInfo( + buildNumber: buildNumberMandatory, + url: buildAppUrl, + changelog: changelog, + isOptional: false, + ); + } else if (buildNumberCurrent > 0 && + buildNumberCurrent < buildNumberOptional) { + // Optional new build is available + final String buildAppUrl = buildRemoteInfo['optional']['url'] ?? null; + final String changelog = + decodeBase64(buildRemoteInfo['optional']['changelog'] ?? ''); + + return UpdateInfo( + buildNumber: buildNumberOptional, + url: buildAppUrl, + changelog: changelog, + isOptional: true, + ); + } + } catch (err) { + // Something went wrong + dev.log(err.toString()); + } + return null; + } +} diff --git a/packages/update_checker/lib/utils/misc.dart b/packages/update_checker/lib/utils/misc.dart new file mode 100644 index 000000000..f2e71c062 --- /dev/null +++ b/packages/update_checker/lib/utils/misc.dart @@ -0,0 +1,14 @@ +import 'dart:convert'; + +/// Decode string if it is Base64 else return as it is +String decodeBase64(String str) { + final Codec stringToBase64 = utf8.fuse(base64); + final RegExp _base64 = RegExp( + r'^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$'); + + if (_base64.hasMatch(str)) { + return stringToBase64.decode(str); + } + + return str; +} diff --git a/packages/update_checker/lib/widgets/update_check.dart b/packages/update_checker/lib/widgets/update_check.dart new file mode 100644 index 000000000..4b9cb096f --- /dev/null +++ b/packages/update_checker/lib/widgets/update_check.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:update_checker/update_bloc.dart'; +import 'package:update_checker/update_event.dart'; +import 'package:update_checker/update_info.dart'; +import 'package:update_checker/update_state.dart'; + +class UpdateCheck extends StatelessWidget { + final Widget child; + + UpdateCheck({this.child}); + + void showOptionalAlert(BuildContext context, UpdateInfo update) {} + + void showMandatoryPage(BuildContext context, UpdateInfo update) {} + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + var bloc = BlocProvider.of(context); + if (state is UpdateAvailable) { + UpdateInfo info = state.updateInfo; + // Returning future to run Task after Rendering is done + Future.microtask(() { + if (info.isOptional) { + showOptionalAlert(context, info); + } else { + showMandatoryPage(context, info); + } + bloc.add(UpdateCheckMarkSeen()); + }); + } + return child; + }, + ); + } +} diff --git a/packages/update_checker/pubspec.lock b/packages/update_checker/pubspec.lock new file mode 100644 index 000000000..b004830a5 --- /dev/null +++ b/packages/update_checker/pubspec.lock @@ -0,0 +1,523 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "7.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.39.17" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.2" + bloc: + dependency: transitive + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + bloc_test: + dependency: "direct dev" + description: + name: bloc_test + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.3" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.13" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + coverage: + dependency: transitive + description: + name: coverage + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.2" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.4" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + firebase: + dependency: transitive + description: + name: firebase + url: "https://pub.dartlang.org" + source: hosted + version: "7.3.0" + firebase_core: + dependency: transitive + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.5" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+2" + firebase_database: + dependency: "direct main" + description: + name: firebase_database + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.6" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+3" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.4" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.4" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.2" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.4" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.8" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.8" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7" + mockito: + dependency: "direct dev" + description: + name: mockito + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.1" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4" + node_interop: + dependency: transitive + description: + name: node_interop + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + node_io: + dependency: transitive + description: + name: node_io + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.12" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + package_info: + dependency: "direct main" + description: + name: package_info + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.3" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.2+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.23.1" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.9" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shelf_static: + dependency: transitive + description: + name: shelf_static + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.8" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.9" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.5" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test: + dependency: "direct dev" + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.2" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.17" + test_core: + dependency: transitive + description: + name: test_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.10" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+15" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.3" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" +sdks: + dart: ">=2.9.0-14.0.dev <3.0.0" + flutter: ">=1.17.0 <2.0.0" diff --git a/packages/update_checker/pubspec.yaml b/packages/update_checker/pubspec.yaml new file mode 100644 index 000000000..f935f7f3c --- /dev/null +++ b/packages/update_checker/pubspec.yaml @@ -0,0 +1,24 @@ +name: update_checker +description: A new Flutter package project. +version: 0.0.1 +author: +homepage: + +environment: + sdk: ">=2.7.0 <3.0.0" + flutter: ">=1.17.0 <2.0.0" + +dependencies: + flutter: + sdk: flutter + firebase_database: ^3.1.6 + package_info: ^0.4.0+13 + flutter_bloc: ^3.2.0 + equatable: ^1.2.3 + +dev_dependencies: + flutter_test: + sdk: flutter + bloc_test: any + test: any + mockito: any diff --git a/packages/update_checker/test/flutter_update_plugin_test.dart b/packages/update_checker/test/flutter_update_plugin_test.dart new file mode 100644 index 000000000..f3bf5c177 --- /dev/null +++ b/packages/update_checker/test/flutter_update_plugin_test.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:update_checker/update_bloc.dart'; +import 'package:update_checker/update_event.dart'; +import 'package:update_checker/update_info.dart'; +import 'package:update_checker/update_state.dart'; +import 'package:update_checker/utils/firebase_utils.dart'; +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; +import 'package:bloc_test/bloc_test.dart'; + +class MockFirebaseUtils extends Mock implements FirebaseUtils {} + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + + group('Update Bloc Test Cases', () { + MockFirebaseUtils fbUtils; + + var updateInfo = + UpdateInfo(buildNumber: 1, changelog: '', url: '', isOptional: false); + var updateInfo2 = + UpdateInfo(buildNumber: 2, changelog: '', url: '', isOptional: true); + + setUp(() { + fbUtils = MockFirebaseUtils(); + }); + + test('Initial State', () { + var updateCheckBloc = UpdateCheckBloc(fbUtils: fbUtils); + expect(updateCheckBloc.state, UpdateNotAvailable()); + }); + + blocTest('Update not available', + build: () async { + when(fbUtils.fetchBuildNumbers()).thenAnswer((_) { + return null; + }); + return UpdateCheckBloc(fbUtils: fbUtils); + }, + act: (bloc) => bloc.add(UpdateCheckStart()), + skip: 0, + expect: [UpdateNotAvailable()]); + + blocTest('Update available', + build: () async { + when(fbUtils.fetchBuildNumbers()).thenAnswer((_) async { + return updateInfo; + }); + return UpdateCheckBloc(fbUtils: fbUtils); + }, + act: (bloc) => bloc.add(UpdateCheckStart()), + expect: [UpdateAvailable(updateInfo)]); + + blocTest( + 'Update available ', + build: () async { + when(fbUtils.fetchBuildNumbers()).thenAnswer((_) async { + return updateInfo2; + }); + return UpdateCheckBloc(fbUtils: fbUtils); + }, + act: (bloc) => bloc.add(UpdateCheckStart()), + expect: [UpdateAvailable(updateInfo2)], + ); + }); +} From 9beb3843bc3910a9b99fdfec41ada62319800475 Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Tue, 1 Sep 2020 12:15:33 +0800 Subject: [PATCH 02/21] add Readme --- packages/update_checker/README.md | 89 +++++++++++++++++-- .../lib/utils/firebase_utils.dart | 2 +- 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/packages/update_checker/README.md b/packages/update_checker/README.md index 14758cc3a..7d8ce0811 100644 --- a/packages/update_checker/README.md +++ b/packages/update_checker/README.md @@ -1,14 +1,85 @@ # update_checker -A new Flutter package project. +Flutter plugin to help check if there is a new update available for the app. +The plugin uses Firebase Database to fetch data. +Firebase Database should look like as follows -## Getting Started +``` +build: + android: + optional: + buildnumer: 24 + changelog: 'RFAyUCB2ZXJ....' // Base64 + url: 'https://alternative.app.link' + mandatory: + buildnumber: 18 + changelog: 'RFAyUCB2ZXJ....' // Base64 + url: 'https://alternative.app.link' +``` -This project is a starting point for a Dart -[package](https://flutter.dev/developing-packages/), -a library module containing code that can be shared easily across -multiple Flutter or Dart projects. +### Types of Udpates -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +There can be an optional update or mandatory update. +For optional updates, users can dismiss the alert and continue using old version. +But for mandatory updates, we should block the usage of app unless user updates. + +### Fields + +* buildnumber - this is an integer to represent the minimum buildnumber required +* changelog - this is base64 encoded changelog +* url - an optional URL to redirect user to instead of App Store / Play Store + +### Buildnumber + +Current installed buildnumber for the application is obtained using `PackageInfo` +This buildnumer is picked from `pubspec.yaml`. E.g. + +``` +name: otc_cashier +description: DP2P is a peer-to-peer payments on deriv.com +version: 1.1.3+8 +``` + +`8` from version will be our buildnumber. +buildnumber can also be set manually as in case of circleci builds +E.g. `flutter build --build-number $CIRCLE_BUILD_NUM` + +## How to Use + +``` +class UpdateCheckWidget extends UpdateCheck { + final Widget child; + + UpdateCheckWidget({this.child}) : super(child: child); + + @override + void showOptionalAlert(BuildContext context, UpdateInfo update) { + // Show optional alert + } + + @override + void showMandatoryPage(BuildContext context, UpdateInfo update) { + // Show blocking UI + } +} +``` + +This package provides `UpdateCheck` widget which can be extended. +There are two methods `showOptionalAlert` and `showMandatoryPage` +Before using the Widget, add the Bloc + +``` +providers: [ + BlocProvider( + create: (BuildContext context) => UpdateCheckBloc(), + ), +] +``` + +Use `UpdateCheckStart` event to start checking for Update. +Use `UpdateCheckCancel` event to cancel the last request to check update + +``` +BlocProvider.of(context).add(UpdateCheckStart()); +BlocProvider.of(context).add(UpdateCheckCancel()); +``` \ No newline at end of file diff --git a/packages/update_checker/lib/utils/firebase_utils.dart b/packages/update_checker/lib/utils/firebase_utils.dart index a7d57a332..978e4f7f2 100644 --- a/packages/update_checker/lib/utils/firebase_utils.dart +++ b/packages/update_checker/lib/utils/firebase_utils.dart @@ -10,7 +10,7 @@ class FirebaseUtils { Firebase Database looks like as follows build: android: - option: + optional: buildnumer: 24 changelog: 'RFAyUCB2ZXJ....' // Base64 url: 'https://alternative.app.link' From 55ca48e8c599d0ee627d9fbe4a3a64346dcb239a Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Fri, 4 Sep 2020 17:00:40 +0800 Subject: [PATCH 03/21] add comments --- README.md | 4 ++++ packages/update_checker/README.md | 4 +++- packages/update_checker/lib/update_bloc.dart | 5 +++++ packages/update_checker/lib/update_event.dart | 5 +++++ packages/update_checker/lib/update_info.dart | 5 +++++ packages/update_checker/lib/update_state.dart | 5 +++++ packages/update_checker/lib/widgets/update_check.dart | 6 ++++++ 7 files changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 37c6f189a..d491f078d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # flutter-deriv-packages Flutter components used in our mobile apps + +## Available Components + +* update_checker - Helps in retreiving information regarding update availability based on custom data stored in Firebase. \ No newline at end of file diff --git a/packages/update_checker/README.md b/packages/update_checker/README.md index 7d8ce0811..7dd9a119d 100644 --- a/packages/update_checker/README.md +++ b/packages/update_checker/README.md @@ -2,7 +2,9 @@ Flutter plugin to help check if there is a new update available for the app. The plugin uses Firebase Database to fetch data. -Firebase Database should look like as follows +Firebase Database should look like as follows. +This Firebase Database needs to be updated manually or through automation +whenever there is a new release. ``` build: diff --git a/packages/update_checker/lib/update_bloc.dart b/packages/update_checker/lib/update_bloc.dart index 14050b4f2..e6375028a 100644 --- a/packages/update_checker/lib/update_bloc.dart +++ b/packages/update_checker/lib/update_bloc.dart @@ -4,6 +4,11 @@ import 'package:update_checker/update_info.dart'; import 'package:update_checker/update_state.dart'; import 'package:update_checker/utils/firebase_utils.dart'; +/// UpdateCheckBloc helps in retreiving Update Information from FireBase. +/// UpdateCheckStart event can be used to fetch the Update Information. +/// This event is added by default when UpdateCheckBloc is initialised. +/// Use UpdateCheckCancel event to cancel the request. +/// And UpdateCheckMarkSeen event when update alert has been shown to user. class UpdateCheckBloc extends Bloc { bool _updateCancelled = false; FirebaseUtils firebaseUtils; diff --git a/packages/update_checker/lib/update_event.dart b/packages/update_checker/lib/update_event.dart index 3237585fa..682d1ae2a 100644 --- a/packages/update_checker/lib/update_event.dart +++ b/packages/update_checker/lib/update_event.dart @@ -1,10 +1,12 @@ import 'package:equatable/equatable.dart'; +/// Abstract Event class for being used in UpdateCheckBloc abstract class UpdateCheckEvent extends Equatable { @override List get props => []; } +/// Start checking the if Update is available or not class UpdateCheckStart extends UpdateCheckEvent { @override String toString() { @@ -12,6 +14,7 @@ class UpdateCheckStart extends UpdateCheckEvent { } } +/// Update availability is being checked class UpdateCheckInProgress extends UpdateCheckEvent { @override String toString() { @@ -19,6 +22,7 @@ class UpdateCheckInProgress extends UpdateCheckEvent { } } +/// Cancel the request to check for update availability class UpdateCheckCancel extends UpdateCheckEvent { @override String toString() { @@ -26,6 +30,7 @@ class UpdateCheckCancel extends UpdateCheckEvent { } } +/// Mark Update check as seen by user class UpdateCheckMarkSeen extends UpdateCheckEvent { @override String toString() { diff --git a/packages/update_checker/lib/update_info.dart b/packages/update_checker/lib/update_info.dart index bfc2ad9d4..6f7f318a6 100644 --- a/packages/update_checker/lib/update_info.dart +++ b/packages/update_checker/lib/update_info.dart @@ -1,3 +1,8 @@ +/// Represents information regarding Updates +/// buildNumber represents minimum build required +/// changelog shows what's new in latest buildnumber +/// url can be empty. When available should be used instead of App/Play Store +/// isOptional is Used to represent is Update is Optional or Mandatory class UpdateInfo { final int buildNumber; final String changelog; diff --git a/packages/update_checker/lib/update_state.dart b/packages/update_checker/lib/update_state.dart index 3d980177f..b7d3ac493 100644 --- a/packages/update_checker/lib/update_state.dart +++ b/packages/update_checker/lib/update_state.dart @@ -1,11 +1,13 @@ import 'package:equatable/equatable.dart'; import 'package:update_checker/update_info.dart'; +/// Abstract State class for being used in UpdateCheckBloc abstract class UpdateCheckState extends Equatable { @override List get props => []; } +/// State to represent when there is no Update Available class UpdateNotAvailable extends UpdateCheckState { @override String toString() { @@ -13,6 +15,7 @@ class UpdateNotAvailable extends UpdateCheckState { } } +/// State to represent when an Update is Available class UpdateAvailable extends UpdateCheckState { final UpdateInfo updateInfo; @@ -27,6 +30,7 @@ class UpdateAvailable extends UpdateCheckState { } } +/// State to represent when Update Check was cancelled class UpdateCancelled extends UpdateCheckState { @override String toString() { @@ -34,6 +38,7 @@ class UpdateCancelled extends UpdateCheckState { } } +/// State to represent when Update alert was seen by user class UpdateSeen extends UpdateCheckState { @override String toString() { diff --git a/packages/update_checker/lib/widgets/update_check.dart b/packages/update_checker/lib/widgets/update_check.dart index 4b9cb096f..eb3e22e91 100644 --- a/packages/update_checker/lib/widgets/update_check.dart +++ b/packages/update_checker/lib/widgets/update_check.dart @@ -5,6 +5,12 @@ import 'package:update_checker/update_event.dart'; import 'package:update_checker/update_info.dart'; import 'package:update_checker/update_state.dart'; +/// UpdateCheck helps in easily integrating logic for checking Updates. +/// You can simply extend this class and override showOptionalAlert and +/// showMandatoryPage methods. The extended widget can be inserted into +/// application where ever you wish to show the alert. +/// Use showOptionalAlert for showing Optional Alerts +/// And showMandatoryPage for mandatory updates class UpdateCheck extends StatelessWidget { final Widget child; From 7656f71d8317ef1cb36344636de9ac57de437431 Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Mon, 7 Sep 2020 11:47:53 +0800 Subject: [PATCH 04/21] update documentation --- README.md | 2 +- packages/update_checker/CHANGELOG.md | 5 ++-- packages/update_checker/README.md | 2 +- .../lib/flutter_update_plugin.dart | 1 - packages/update_checker/lib/update_bloc.dart | 18 ++++++------ packages/update_checker/lib/update_event.dart | 2 +- packages/update_checker/lib/update_info.dart | 11 +++++--- packages/update_checker/lib/update_state.dart | 2 +- .../lib/utils/firebase_utils.dart | 28 ++++++++++--------- .../lib/widgets/update_check.dart | 11 ++++---- 10 files changed, 45 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index d491f078d..617274442 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,4 @@ Flutter components used in our mobile apps ## Available Components -* update_checker - Helps in retreiving information regarding update availability based on custom data stored in Firebase. \ No newline at end of file +* [update_checker](./packages/update_checker) - Helps in retreiving information regarding update availability based on custom data stored in Firebase. \ No newline at end of file diff --git a/packages/update_checker/CHANGELOG.md b/packages/update_checker/CHANGELOG.md index ac071598e..5912167de 100644 --- a/packages/update_checker/CHANGELOG.md +++ b/packages/update_checker/CHANGELOG.md @@ -1,3 +1,4 @@ -## [0.0.1] - TODO: Add release date. +## [0.0.1] -* TODO: Describe initial release. +* Checks for Update details from FireBase +* Updates can be optional or mandatory diff --git a/packages/update_checker/README.md b/packages/update_checker/README.md index 7dd9a119d..0ba11c369 100644 --- a/packages/update_checker/README.md +++ b/packages/update_checker/README.md @@ -66,7 +66,7 @@ class UpdateCheckWidget extends UpdateCheck { } ``` -This package provides `UpdateCheck` widget which can be extended. +This package provides [UpdateCheck](./lib/widgets/update_check.dart) widget which can be extended. There are two methods `showOptionalAlert` and `showMandatoryPage` Before using the Widget, add the Bloc diff --git a/packages/update_checker/lib/flutter_update_plugin.dart b/packages/update_checker/lib/flutter_update_plugin.dart index 89f581434..d00850482 100644 --- a/packages/update_checker/lib/flutter_update_plugin.dart +++ b/packages/update_checker/lib/flutter_update_plugin.dart @@ -1,2 +1 @@ library flutter_update_plugin; - diff --git a/packages/update_checker/lib/update_bloc.dart b/packages/update_checker/lib/update_bloc.dart index e6375028a..eb1d799f5 100644 --- a/packages/update_checker/lib/update_bloc.dart +++ b/packages/update_checker/lib/update_bloc.dart @@ -4,11 +4,11 @@ import 'package:update_checker/update_info.dart'; import 'package:update_checker/update_state.dart'; import 'package:update_checker/utils/firebase_utils.dart'; -/// UpdateCheckBloc helps in retreiving Update Information from FireBase. -/// UpdateCheckStart event can be used to fetch the Update Information. -/// This event is added by default when UpdateCheckBloc is initialised. -/// Use UpdateCheckCancel event to cancel the request. -/// And UpdateCheckMarkSeen event when update alert has been shown to user. +/// [UpdateCheckBloc] helps in retreiving Update Information from FireBase. +/// [UpdateCheckStart] event can be used to fetch the Update Information. +/// This event is added by default when [UpdateCheckBloc] is initialised. +/// Use [UpdateCheckCancel] event to cancel the request. +/// And [UpdateCheckMarkSeen] event when update alert has been shown to user. class UpdateCheckBloc extends Bloc { bool _updateCancelled = false; FirebaseUtils firebaseUtils; @@ -16,6 +16,9 @@ class UpdateCheckBloc extends Bloc { @override UpdateCheckState get initialState => UpdateNotAvailable(); + /// Constructor for [UpdateCheckBloc] + /// An optional mock [FirebaseUtils] instance can be provided for testing + /// For normal use don't provide fbUtils, it will be constructed itself UpdateCheckBloc({FirebaseUtils fbUtils}) { this.firebaseUtils = fbUtils ?? FirebaseUtils(); this.add(UpdateCheckStart()); @@ -28,10 +31,9 @@ class UpdateCheckBloc extends Bloc { this.add(UpdateCheckInProgress()); } else if (event is UpdateCheckInProgress) { UpdateInfo info = await firebaseUtils.fetchBuildNumbers(); - if (info != null && _updateCancelled == false){ + if (info != null && _updateCancelled == false) { yield UpdateAvailable(info); - } - else { + } else { yield UpdateNotAvailable(); } } else if (event is UpdateCheckCancel) { diff --git a/packages/update_checker/lib/update_event.dart b/packages/update_checker/lib/update_event.dart index 682d1ae2a..05ffad4c0 100644 --- a/packages/update_checker/lib/update_event.dart +++ b/packages/update_checker/lib/update_event.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; -/// Abstract Event class for being used in UpdateCheckBloc +/// Abstract Event class for being used in [UpdateCheckBloc] abstract class UpdateCheckEvent extends Equatable { @override List get props => []; diff --git a/packages/update_checker/lib/update_info.dart b/packages/update_checker/lib/update_info.dart index 6f7f318a6..6d3bcce1c 100644 --- a/packages/update_checker/lib/update_info.dart +++ b/packages/update_checker/lib/update_info.dart @@ -1,12 +1,15 @@ /// Represents information regarding Updates -/// buildNumber represents minimum build required -/// changelog shows what's new in latest buildnumber -/// url can be empty. When available should be used instead of App/Play Store -/// isOptional is Used to represent is Update is Optional or Mandatory class UpdateInfo { + /// buildNumber represents minimum build required final int buildNumber; + + /// changelog shows what's new in latest buildnumber final String changelog; + + /// url can be empty. When available should be used instead of App/Play Store final String url; + + /// isOptional is Used to represent is Update is Optional or Mandatory final bool isOptional; UpdateInfo({ diff --git a/packages/update_checker/lib/update_state.dart b/packages/update_checker/lib/update_state.dart index b7d3ac493..c5ca148f3 100644 --- a/packages/update_checker/lib/update_state.dart +++ b/packages/update_checker/lib/update_state.dart @@ -1,7 +1,7 @@ import 'package:equatable/equatable.dart'; import 'package:update_checker/update_info.dart'; -/// Abstract State class for being used in UpdateCheckBloc +/// Abstract State class for being used in [UpdateCheckBloc] abstract class UpdateCheckState extends Equatable { @override List get props => []; diff --git a/packages/update_checker/lib/utils/firebase_utils.dart b/packages/update_checker/lib/utils/firebase_utils.dart index 978e4f7f2..a19dfdaac 100644 --- a/packages/update_checker/lib/utils/firebase_utils.dart +++ b/packages/update_checker/lib/utils/firebase_utils.dart @@ -5,20 +5,22 @@ import 'package:update_checker/update_info.dart'; import 'package:update_checker/utils/misc.dart'; import 'package:package_info/package_info.dart'; +/// [FirebaseUtils] class provides [fetchBuildNumbers] method which can be used +/// to fetch update details from FireBase +/// +/// Firebase Database looks like as follows +/// build: +/// android: +/// optional: +/// buildnumer: 24 +/// changelog: 'RFAyUCB2ZXJ....' // Base64 +/// url: 'https://alternative.app.link' +/// mandatory: +/// buildnumber: 18 +/// changelog: 'RFAyUCB2ZXJ....' // Base64 +/// url: 'https://alternative.app.link' class FirebaseUtils { -/* - Firebase Database looks like as follows - build: - android: - optional: - buildnumer: 24 - changelog: 'RFAyUCB2ZXJ....' // Base64 - url: 'https://alternative.app.link' - mandatory: - buildnumber: 18 - changelog: 'RFAyUCB2ZXJ....' // Base64 - url: 'https://alternative.app.link' - */ + /// Fetch the Update details Future fetchBuildNumbers() async { try { String platform = Platform.operatingSystem; diff --git a/packages/update_checker/lib/widgets/update_check.dart b/packages/update_checker/lib/widgets/update_check.dart index eb3e22e91..8c4128f8d 100644 --- a/packages/update_checker/lib/widgets/update_check.dart +++ b/packages/update_checker/lib/widgets/update_check.dart @@ -5,19 +5,20 @@ import 'package:update_checker/update_event.dart'; import 'package:update_checker/update_info.dart'; import 'package:update_checker/update_state.dart'; -/// UpdateCheck helps in easily integrating logic for checking Updates. -/// You can simply extend this class and override showOptionalAlert and -/// showMandatoryPage methods. The extended widget can be inserted into +/// [UpdateCheck] helps in easily integrating logic for checking Updates. +/// You can simply extend this class and override [showOptionalAlert] and +/// [showMandatoryPage] methods. The extended widget can be inserted into /// application where ever you wish to show the alert. -/// Use showOptionalAlert for showing Optional Alerts -/// And showMandatoryPage for mandatory updates class UpdateCheck extends StatelessWidget { + /// Use this widget as parent to any [child] widget final Widget child; UpdateCheck({this.child}); + /// Use [showOptionalAlert] for showing Optional Alerts void showOptionalAlert(BuildContext context, UpdateInfo update) {} + /// Use [showMandatoryPage] for mandatory updates void showMandatoryPage(BuildContext context, UpdateInfo update) {} @override From 6fb1706a99ebad8040a07c7c08b86f620a97de59 Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Mon, 7 Sep 2020 14:44:08 +0800 Subject: [PATCH 05/21] apply suggestions from PR review --- packages/update_checker/CHANGELOG.md | 2 +- packages/update_checker/README.md | 9 +++------ .../lib/{utils => src}/firebase_utils.dart | 14 +++++++------- .../update_checker/lib/{utils => src}/misc.dart | 3 ++- packages/update_checker/lib/update_bloc.dart | 14 +++++++------- packages/update_checker/lib/update_event.dart | 10 +++++----- packages/update_checker/lib/update_info.dart | 10 +++++----- packages/update_checker/lib/update_state.dart | 10 +++++----- .../update_checker/lib/widgets/update_check.dart | 12 ++++++------ .../test/flutter_update_plugin_test.dart | 2 +- 10 files changed, 42 insertions(+), 44 deletions(-) rename packages/update_checker/lib/{utils => src}/firebase_utils.dart (86%) rename packages/update_checker/lib/{utils => src}/misc.dart (79%) diff --git a/packages/update_checker/CHANGELOG.md b/packages/update_checker/CHANGELOG.md index 5912167de..cf002b04d 100644 --- a/packages/update_checker/CHANGELOG.md +++ b/packages/update_checker/CHANGELOG.md @@ -1,4 +1,4 @@ ## [0.0.1] -* Checks for Update details from FireBase +* Checks for Update details from Firebase * Updates can be optional or mandatory diff --git a/packages/update_checker/README.md b/packages/update_checker/README.md index 0ba11c369..4c50c0041 100644 --- a/packages/update_checker/README.md +++ b/packages/update_checker/README.md @@ -1,10 +1,7 @@ # update_checker -Flutter plugin to help check if there is a new update available for the app. -The plugin uses Firebase Database to fetch data. -Firebase Database should look like as follows. -This Firebase Database needs to be updated manually or through automation -whenever there is a new release. +update_checker plugin checks for the availability of new updates for the app using it. +The plugin uses Firebase Database which needs to be updated manually or through automation whenever there is a new release. Firebase Database is used to fetch data which looks like the following: ``` build: @@ -29,7 +26,7 @@ But for mandatory updates, we should block the usage of app unless user updates. * buildnumber - this is an integer to represent the minimum buildnumber required * changelog - this is base64 encoded changelog -* url - an optional URL to redirect user to instead of App Store / Play Store +* url - an optional URL to redirect user to, instead of App Store / Play Store ### Buildnumber diff --git a/packages/update_checker/lib/utils/firebase_utils.dart b/packages/update_checker/lib/src/firebase_utils.dart similarity index 86% rename from packages/update_checker/lib/utils/firebase_utils.dart rename to packages/update_checker/lib/src/firebase_utils.dart index a19dfdaac..70015aafd 100644 --- a/packages/update_checker/lib/utils/firebase_utils.dart +++ b/packages/update_checker/lib/src/firebase_utils.dart @@ -2,13 +2,13 @@ import 'dart:io'; import 'dart:developer' as dev; import 'package:firebase_database/firebase_database.dart'; import 'package:update_checker/update_info.dart'; -import 'package:update_checker/utils/misc.dart'; +import 'package:update_checker/src/misc.dart'; import 'package:package_info/package_info.dart'; -/// [FirebaseUtils] class provides [fetchBuildNumbers] method which can be used -/// to fetch update details from FireBase +/// FirebaseUtils class provides [fetchBuildNumbers] method which can be used +/// to fetch update details from Firebase. /// -/// Firebase Database looks like as follows +/// Firebase Database update data looks like the following /// build: /// android: /// optional: @@ -20,11 +20,11 @@ import 'package:package_info/package_info.dart'; /// changelog: 'RFAyUCB2ZXJ....' // Base64 /// url: 'https://alternative.app.link' class FirebaseUtils { - /// Fetch the Update details + /// Fetch the Update details. Future fetchBuildNumbers() async { try { - String platform = Platform.operatingSystem; - DatabaseReference dbRef = + final String platform = Platform.operatingSystem; + final DatabaseReference dbRef = FirebaseDatabase.instance.reference().child('build').child(platform); final buildRemoteInfo = (await dbRef.once()).value; diff --git a/packages/update_checker/lib/utils/misc.dart b/packages/update_checker/lib/src/misc.dart similarity index 79% rename from packages/update_checker/lib/utils/misc.dart rename to packages/update_checker/lib/src/misc.dart index f2e71c062..be825842c 100644 --- a/packages/update_checker/lib/utils/misc.dart +++ b/packages/update_checker/lib/src/misc.dart @@ -1,6 +1,7 @@ import 'dart:convert'; -/// Decode string if it is Base64 else return as it is +/// Decodes [str] if it is a Base64, +/// otherwise returns the original value of [str]. String decodeBase64(String str) { final Codec stringToBase64 = utf8.fuse(base64); final RegExp _base64 = RegExp( diff --git a/packages/update_checker/lib/update_bloc.dart b/packages/update_checker/lib/update_bloc.dart index eb1d799f5..97673930e 100644 --- a/packages/update_checker/lib/update_bloc.dart +++ b/packages/update_checker/lib/update_bloc.dart @@ -2,11 +2,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:update_checker/update_event.dart'; import 'package:update_checker/update_info.dart'; import 'package:update_checker/update_state.dart'; -import 'package:update_checker/utils/firebase_utils.dart'; +import 'package:update_checker/src/firebase_utils.dart'; -/// [UpdateCheckBloc] helps in retreiving Update Information from FireBase. +/// UpdateCheckBloc helps in retrieving Update Information from Firebase. /// [UpdateCheckStart] event can be used to fetch the Update Information. -/// This event is added by default when [UpdateCheckBloc] is initialised. +/// This event is added by default when UpdateCheckBloc is initialised. /// Use [UpdateCheckCancel] event to cancel the request. /// And [UpdateCheckMarkSeen] event when update alert has been shown to user. class UpdateCheckBloc extends Bloc { @@ -16,9 +16,9 @@ class UpdateCheckBloc extends Bloc { @override UpdateCheckState get initialState => UpdateNotAvailable(); - /// Constructor for [UpdateCheckBloc] - /// An optional mock [FirebaseUtils] instance can be provided for testing - /// For normal use don't provide fbUtils, it will be constructed itself + /// Constructor for [UpdateCheckBloc]. + /// An optional mock [FirebaseUtils] instance can be provided for testing. + /// For normal use don't provide fbUtils, it will be constructed itself. UpdateCheckBloc({FirebaseUtils fbUtils}) { this.firebaseUtils = fbUtils ?? FirebaseUtils(); this.add(UpdateCheckStart()); @@ -31,7 +31,7 @@ class UpdateCheckBloc extends Bloc { this.add(UpdateCheckInProgress()); } else if (event is UpdateCheckInProgress) { UpdateInfo info = await firebaseUtils.fetchBuildNumbers(); - if (info != null && _updateCancelled == false) { + if (info != null && !_updateCancelled) { yield UpdateAvailable(info); } else { yield UpdateNotAvailable(); diff --git a/packages/update_checker/lib/update_event.dart b/packages/update_checker/lib/update_event.dart index 05ffad4c0..cb8274a15 100644 --- a/packages/update_checker/lib/update_event.dart +++ b/packages/update_checker/lib/update_event.dart @@ -1,12 +1,12 @@ import 'package:equatable/equatable.dart'; -/// Abstract Event class for being used in [UpdateCheckBloc] +/// Abstract Event class for being used in UpdateCheckBloc. abstract class UpdateCheckEvent extends Equatable { @override List get props => []; } -/// Start checking the if Update is available or not +/// Start checking if Update is available or not. class UpdateCheckStart extends UpdateCheckEvent { @override String toString() { @@ -14,7 +14,7 @@ class UpdateCheckStart extends UpdateCheckEvent { } } -/// Update availability is being checked +/// Update availability is being checked. class UpdateCheckInProgress extends UpdateCheckEvent { @override String toString() { @@ -22,7 +22,7 @@ class UpdateCheckInProgress extends UpdateCheckEvent { } } -/// Cancel the request to check for update availability +/// Cancel the request to check for update availability. class UpdateCheckCancel extends UpdateCheckEvent { @override String toString() { @@ -30,7 +30,7 @@ class UpdateCheckCancel extends UpdateCheckEvent { } } -/// Mark Update check as seen by user +/// Mark Update check as seen by user. class UpdateCheckMarkSeen extends UpdateCheckEvent { @override String toString() { diff --git a/packages/update_checker/lib/update_info.dart b/packages/update_checker/lib/update_info.dart index 6d3bcce1c..1200999fd 100644 --- a/packages/update_checker/lib/update_info.dart +++ b/packages/update_checker/lib/update_info.dart @@ -1,15 +1,15 @@ -/// Represents information regarding Updates +/// Represents information regarding Updates. class UpdateInfo { - /// buildNumber represents minimum build required + /// [buildNumber] represents the minimum build required. final int buildNumber; - /// changelog shows what's new in latest buildnumber + /// [changelog] shows what's new in latest buildnumber. final String changelog; - /// url can be empty. When available should be used instead of App/Play Store + /// [url] can be empty. When available should be used instead of App/Play Store. final String url; - /// isOptional is Used to represent is Update is Optional or Mandatory + /// [isOptional] is used to represent is Update is Optional or Mandatory. final bool isOptional; UpdateInfo({ diff --git a/packages/update_checker/lib/update_state.dart b/packages/update_checker/lib/update_state.dart index c5ca148f3..2ee5f0abe 100644 --- a/packages/update_checker/lib/update_state.dart +++ b/packages/update_checker/lib/update_state.dart @@ -1,13 +1,13 @@ import 'package:equatable/equatable.dart'; import 'package:update_checker/update_info.dart'; -/// Abstract State class for being used in [UpdateCheckBloc] +/// Abstract State class for being used in UpdateCheckBloc abstract class UpdateCheckState extends Equatable { @override List get props => []; } -/// State to represent when there is no Update Available +/// State to represent when there is no Update Available. class UpdateNotAvailable extends UpdateCheckState { @override String toString() { @@ -15,7 +15,7 @@ class UpdateNotAvailable extends UpdateCheckState { } } -/// State to represent when an Update is Available +/// State to represent when an Update is Available. class UpdateAvailable extends UpdateCheckState { final UpdateInfo updateInfo; @@ -30,7 +30,7 @@ class UpdateAvailable extends UpdateCheckState { } } -/// State to represent when Update Check was cancelled +/// State to represent when Update Check was cancelled. class UpdateCancelled extends UpdateCheckState { @override String toString() { @@ -38,7 +38,7 @@ class UpdateCancelled extends UpdateCheckState { } } -/// State to represent when Update alert was seen by user +/// State to represent when Update alert was seen by user. class UpdateSeen extends UpdateCheckState { @override String toString() { diff --git a/packages/update_checker/lib/widgets/update_check.dart b/packages/update_checker/lib/widgets/update_check.dart index 8c4128f8d..089543750 100644 --- a/packages/update_checker/lib/widgets/update_check.dart +++ b/packages/update_checker/lib/widgets/update_check.dart @@ -5,20 +5,20 @@ import 'package:update_checker/update_event.dart'; import 'package:update_checker/update_info.dart'; import 'package:update_checker/update_state.dart'; -/// [UpdateCheck] helps in easily integrating logic for checking Updates. +/// UpdateCheck helps in easily integrating logic for checking Updates. /// You can simply extend this class and override [showOptionalAlert] and -/// [showMandatoryPage] methods. The extended widget can be inserted into -/// application where ever you wish to show the alert. +/// [showMandatoryPage] methods. The extended widget can be inserted to +/// show alert anywhere in the app. class UpdateCheck extends StatelessWidget { - /// Use this widget as parent to any [child] widget + /// [child] widget that should show the update alert dialog. final Widget child; UpdateCheck({this.child}); - /// Use [showOptionalAlert] for showing Optional Alerts + /// Use [showOptionalAlert] for showing Optional Alerts. void showOptionalAlert(BuildContext context, UpdateInfo update) {} - /// Use [showMandatoryPage] for mandatory updates + /// Use [showMandatoryPage] for mandatory updates. void showMandatoryPage(BuildContext context, UpdateInfo update) {} @override diff --git a/packages/update_checker/test/flutter_update_plugin_test.dart b/packages/update_checker/test/flutter_update_plugin_test.dart index f3bf5c177..6e32ff646 100644 --- a/packages/update_checker/test/flutter_update_plugin_test.dart +++ b/packages/update_checker/test/flutter_update_plugin_test.dart @@ -3,7 +3,7 @@ import 'package:update_checker/update_bloc.dart'; import 'package:update_checker/update_event.dart'; import 'package:update_checker/update_info.dart'; import 'package:update_checker/update_state.dart'; -import 'package:update_checker/utils/firebase_utils.dart'; +import 'package:update_checker/src/firebase_utils.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import 'package:bloc_test/bloc_test.dart'; From f5151a184f76a7bc82763276bc37d87a990ecf5c Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Mon, 7 Sep 2020 14:52:13 +0800 Subject: [PATCH 06/21] update deps --- packages/update_checker/pubspec.lock | 10 +++++----- packages/update_checker/pubspec.yaml | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/update_checker/pubspec.lock b/packages/update_checker/pubspec.lock index b004830a5..a725e494f 100644 --- a/packages/update_checker/pubspec.lock +++ b/packages/update_checker/pubspec.lock @@ -140,28 +140,28 @@ packages: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.5" + version: "0.5.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.0" firebase_core_web: dependency: transitive description: name: firebase_core_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+2" + version: "0.2.0" firebase_database: dependency: "direct main" description: name: firebase_database url: "https://pub.dartlang.org" source: hosted - version: "3.1.6" + version: "4.0.0" flutter: dependency: "direct main" description: flutter @@ -344,7 +344,7 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "4.3.2+1" + version: "4.3.2+2" pub_semver: dependency: transitive description: diff --git a/packages/update_checker/pubspec.yaml b/packages/update_checker/pubspec.yaml index f935f7f3c..5eb8727ef 100644 --- a/packages/update_checker/pubspec.yaml +++ b/packages/update_checker/pubspec.yaml @@ -11,10 +11,10 @@ environment: dependencies: flutter: sdk: flutter - firebase_database: ^3.1.6 - package_info: ^0.4.0+13 + firebase_database: ^4.0.0 + package_info: ^0.4.3 flutter_bloc: ^3.2.0 - equatable: ^1.2.3 + equatable: ^1.2.4 dev_dependencies: flutter_test: From 7188a184363fafe0601f0fa904a3f42937e40978 Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Mon, 7 Sep 2020 15:25:41 +0800 Subject: [PATCH 07/21] update flutter_bloc package --- packages/update_checker/lib/update_bloc.dart | 5 +---- packages/update_checker/pubspec.lock | 13 +++---------- packages/update_checker/pubspec.yaml | 8 ++++---- .../test/flutter_update_plugin_test.dart | 6 +++--- 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/packages/update_checker/lib/update_bloc.dart b/packages/update_checker/lib/update_bloc.dart index 97673930e..7ce88b2ec 100644 --- a/packages/update_checker/lib/update_bloc.dart +++ b/packages/update_checker/lib/update_bloc.dart @@ -13,13 +13,10 @@ class UpdateCheckBloc extends Bloc { bool _updateCancelled = false; FirebaseUtils firebaseUtils; - @override - UpdateCheckState get initialState => UpdateNotAvailable(); - /// Constructor for [UpdateCheckBloc]. /// An optional mock [FirebaseUtils] instance can be provided for testing. /// For normal use don't provide fbUtils, it will be constructed itself. - UpdateCheckBloc({FirebaseUtils fbUtils}) { + UpdateCheckBloc({FirebaseUtils fbUtils}) : super(UpdateNotAvailable()) { this.firebaseUtils = fbUtils ?? FirebaseUtils(); this.add(UpdateCheckStart()); } diff --git a/packages/update_checker/pubspec.lock b/packages/update_checker/pubspec.lock index a725e494f..7df7c1853 100644 --- a/packages/update_checker/pubspec.lock +++ b/packages/update_checker/pubspec.lock @@ -35,14 +35,14 @@ packages: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "6.0.3" bloc_test: dependency: "direct dev" description: name: bloc_test url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "7.0.3" boolean_selector: dependency: transitive description: @@ -173,7 +173,7 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "3.2.0" + version: "6.0.5" flutter_test: dependency: "direct dev" description: flutter @@ -359,13 +359,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" - rxdart: - dependency: transitive - description: - name: rxdart - url: "https://pub.dartlang.org" - source: hosted - version: "0.23.1" shelf: dependency: transitive description: diff --git a/packages/update_checker/pubspec.yaml b/packages/update_checker/pubspec.yaml index 5eb8727ef..7597297c1 100644 --- a/packages/update_checker/pubspec.yaml +++ b/packages/update_checker/pubspec.yaml @@ -13,12 +13,12 @@ dependencies: sdk: flutter firebase_database: ^4.0.0 package_info: ^0.4.3 - flutter_bloc: ^3.2.0 + flutter_bloc: ^6.0.5 equatable: ^1.2.4 dev_dependencies: flutter_test: sdk: flutter - bloc_test: any - test: any - mockito: any + bloc_test: ^7.0.3 + test: ^1.15.2 + mockito: ^4.1.1 diff --git a/packages/update_checker/test/flutter_update_plugin_test.dart b/packages/update_checker/test/flutter_update_plugin_test.dart index 6e32ff646..b8c362ac3 100644 --- a/packages/update_checker/test/flutter_update_plugin_test.dart +++ b/packages/update_checker/test/flutter_update_plugin_test.dart @@ -31,7 +31,7 @@ void main() { }); blocTest('Update not available', - build: () async { + build: () { when(fbUtils.fetchBuildNumbers()).thenAnswer((_) { return null; }); @@ -42,7 +42,7 @@ void main() { expect: [UpdateNotAvailable()]); blocTest('Update available', - build: () async { + build: () { when(fbUtils.fetchBuildNumbers()).thenAnswer((_) async { return updateInfo; }); @@ -53,7 +53,7 @@ void main() { blocTest( 'Update available ', - build: () async { + build: () { when(fbUtils.fetchBuildNumbers()).thenAnswer((_) async { return updateInfo2; }); From 4d6fdf1ea3feeda874ed66f67b4f659038d64e2f Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Mon, 7 Sep 2020 15:29:56 +0800 Subject: [PATCH 08/21] Revert "update flutter_bloc package" This reverts commit 7188a184363fafe0601f0fa904a3f42937e40978. --- packages/update_checker/lib/update_bloc.dart | 5 ++++- packages/update_checker/pubspec.lock | 13 ++++++++++--- packages/update_checker/pubspec.yaml | 8 ++++---- .../test/flutter_update_plugin_test.dart | 6 +++--- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/update_checker/lib/update_bloc.dart b/packages/update_checker/lib/update_bloc.dart index 7ce88b2ec..97673930e 100644 --- a/packages/update_checker/lib/update_bloc.dart +++ b/packages/update_checker/lib/update_bloc.dart @@ -13,10 +13,13 @@ class UpdateCheckBloc extends Bloc { bool _updateCancelled = false; FirebaseUtils firebaseUtils; + @override + UpdateCheckState get initialState => UpdateNotAvailable(); + /// Constructor for [UpdateCheckBloc]. /// An optional mock [FirebaseUtils] instance can be provided for testing. /// For normal use don't provide fbUtils, it will be constructed itself. - UpdateCheckBloc({FirebaseUtils fbUtils}) : super(UpdateNotAvailable()) { + UpdateCheckBloc({FirebaseUtils fbUtils}) { this.firebaseUtils = fbUtils ?? FirebaseUtils(); this.add(UpdateCheckStart()); } diff --git a/packages/update_checker/pubspec.lock b/packages/update_checker/pubspec.lock index 7df7c1853..a725e494f 100644 --- a/packages/update_checker/pubspec.lock +++ b/packages/update_checker/pubspec.lock @@ -35,14 +35,14 @@ packages: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "6.0.3" + version: "3.0.0" bloc_test: dependency: "direct dev" description: name: bloc_test url: "https://pub.dartlang.org" source: hosted - version: "7.0.3" + version: "4.0.0" boolean_selector: dependency: transitive description: @@ -173,7 +173,7 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "6.0.5" + version: "3.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -359,6 +359,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.23.1" shelf: dependency: transitive description: diff --git a/packages/update_checker/pubspec.yaml b/packages/update_checker/pubspec.yaml index 7597297c1..5eb8727ef 100644 --- a/packages/update_checker/pubspec.yaml +++ b/packages/update_checker/pubspec.yaml @@ -13,12 +13,12 @@ dependencies: sdk: flutter firebase_database: ^4.0.0 package_info: ^0.4.3 - flutter_bloc: ^6.0.5 + flutter_bloc: ^3.2.0 equatable: ^1.2.4 dev_dependencies: flutter_test: sdk: flutter - bloc_test: ^7.0.3 - test: ^1.15.2 - mockito: ^4.1.1 + bloc_test: any + test: any + mockito: any diff --git a/packages/update_checker/test/flutter_update_plugin_test.dart b/packages/update_checker/test/flutter_update_plugin_test.dart index b8c362ac3..6e32ff646 100644 --- a/packages/update_checker/test/flutter_update_plugin_test.dart +++ b/packages/update_checker/test/flutter_update_plugin_test.dart @@ -31,7 +31,7 @@ void main() { }); blocTest('Update not available', - build: () { + build: () async { when(fbUtils.fetchBuildNumbers()).thenAnswer((_) { return null; }); @@ -42,7 +42,7 @@ void main() { expect: [UpdateNotAvailable()]); blocTest('Update available', - build: () { + build: () async { when(fbUtils.fetchBuildNumbers()).thenAnswer((_) async { return updateInfo; }); @@ -53,7 +53,7 @@ void main() { blocTest( 'Update available ', - build: () { + build: () async { when(fbUtils.fetchBuildNumbers()).thenAnswer((_) async { return updateInfo2; }); From e54f497dde860618f5db0ba7decbd55860b9c772 Mon Sep 17 00:00:00 2001 From: Suyash Mohan <33442325+suyash-binary@users.noreply.github.com> Date: Mon, 7 Sep 2020 16:40:15 +0800 Subject: [PATCH 09/21] Update packages/update_checker/lib/update_info.dart Co-authored-by: abed-binary <61964755+abed-binary@users.noreply.github.com> --- packages/update_checker/lib/update_info.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/update_checker/lib/update_info.dart b/packages/update_checker/lib/update_info.dart index 1200999fd..1a803e7df 100644 --- a/packages/update_checker/lib/update_info.dart +++ b/packages/update_checker/lib/update_info.dart @@ -9,7 +9,7 @@ class UpdateInfo { /// [url] can be empty. When available should be used instead of App/Play Store. final String url; - /// [isOptional] is used to represent is Update is Optional or Mandatory. + /// [isOptional] is used to represent if Update is Optional or Mandatory. final bool isOptional; UpdateInfo({ From cda15e3bcb17c37e7f7a9f698f8cb7ebc802e232 Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Mon, 28 Sep 2020 10:44:50 +0800 Subject: [PATCH 10/21] update bloc library to 6.0.1 --- packages/update_checker/lib/update_bloc.dart | 5 +---- packages/update_checker/pubspec.lock | 13 +++---------- packages/update_checker/pubspec.yaml | 2 +- .../test/flutter_update_plugin_test.dart | 6 +++--- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/update_checker/lib/update_bloc.dart b/packages/update_checker/lib/update_bloc.dart index 97673930e..7ce88b2ec 100644 --- a/packages/update_checker/lib/update_bloc.dart +++ b/packages/update_checker/lib/update_bloc.dart @@ -13,13 +13,10 @@ class UpdateCheckBloc extends Bloc { bool _updateCancelled = false; FirebaseUtils firebaseUtils; - @override - UpdateCheckState get initialState => UpdateNotAvailable(); - /// Constructor for [UpdateCheckBloc]. /// An optional mock [FirebaseUtils] instance can be provided for testing. /// For normal use don't provide fbUtils, it will be constructed itself. - UpdateCheckBloc({FirebaseUtils fbUtils}) { + UpdateCheckBloc({FirebaseUtils fbUtils}) : super(UpdateNotAvailable()) { this.firebaseUtils = fbUtils ?? FirebaseUtils(); this.add(UpdateCheckStart()); } diff --git a/packages/update_checker/pubspec.lock b/packages/update_checker/pubspec.lock index a725e494f..ae98f1eae 100644 --- a/packages/update_checker/pubspec.lock +++ b/packages/update_checker/pubspec.lock @@ -35,14 +35,14 @@ packages: name: bloc url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "6.0.3" bloc_test: dependency: "direct dev" description: name: bloc_test url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "7.0.4" boolean_selector: dependency: transitive description: @@ -173,7 +173,7 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "3.2.0" + version: "6.0.5" flutter_test: dependency: "direct dev" description: flutter @@ -359,13 +359,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" - rxdart: - dependency: transitive - description: - name: rxdart - url: "https://pub.dartlang.org" - source: hosted - version: "0.23.1" shelf: dependency: transitive description: diff --git a/packages/update_checker/pubspec.yaml b/packages/update_checker/pubspec.yaml index 5eb8727ef..508de4bd0 100644 --- a/packages/update_checker/pubspec.yaml +++ b/packages/update_checker/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: sdk: flutter firebase_database: ^4.0.0 package_info: ^0.4.3 - flutter_bloc: ^3.2.0 + flutter_bloc: ^6.0.1 equatable: ^1.2.4 dev_dependencies: diff --git a/packages/update_checker/test/flutter_update_plugin_test.dart b/packages/update_checker/test/flutter_update_plugin_test.dart index 6e32ff646..b8c362ac3 100644 --- a/packages/update_checker/test/flutter_update_plugin_test.dart +++ b/packages/update_checker/test/flutter_update_plugin_test.dart @@ -31,7 +31,7 @@ void main() { }); blocTest('Update not available', - build: () async { + build: () { when(fbUtils.fetchBuildNumbers()).thenAnswer((_) { return null; }); @@ -42,7 +42,7 @@ void main() { expect: [UpdateNotAvailable()]); blocTest('Update available', - build: () async { + build: () { when(fbUtils.fetchBuildNumbers()).thenAnswer((_) async { return updateInfo; }); @@ -53,7 +53,7 @@ void main() { blocTest( 'Update available ', - build: () async { + build: () { when(fbUtils.fetchBuildNumbers()).thenAnswer((_) async { return updateInfo2; }); From df2526a5c60624806178ed40e097650da719f07d Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Mon, 19 Oct 2020 11:45:35 +0800 Subject: [PATCH 11/21] remove markseen --- packages/update_checker/lib/widgets/update_check.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/update_checker/lib/widgets/update_check.dart b/packages/update_checker/lib/widgets/update_check.dart index 089543750..3698d45c7 100644 --- a/packages/update_checker/lib/widgets/update_check.dart +++ b/packages/update_checker/lib/widgets/update_check.dart @@ -35,7 +35,6 @@ class UpdateCheck extends StatelessWidget { } else { showMandatoryPage(context, info); } - bloc.add(UpdateCheckMarkSeen()); }); } return child; From e5603aedc83c3479d8f0fd0b7aa1b735fced6246 Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Mon, 19 Oct 2020 12:44:04 +0800 Subject: [PATCH 12/21] remove unnecessary statements --- packages/update_checker/lib/widgets/update_check.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/update_checker/lib/widgets/update_check.dart b/packages/update_checker/lib/widgets/update_check.dart index 3698d45c7..031f4089c 100644 --- a/packages/update_checker/lib/widgets/update_check.dart +++ b/packages/update_checker/lib/widgets/update_check.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:update_checker/update_bloc.dart'; -import 'package:update_checker/update_event.dart'; import 'package:update_checker/update_info.dart'; import 'package:update_checker/update_state.dart'; @@ -25,7 +24,6 @@ class UpdateCheck extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - var bloc = BlocProvider.of(context); if (state is UpdateAvailable) { UpdateInfo info = state.updateInfo; // Returning future to run Task after Rendering is done From 9d126f7febacbd622e154329dc7ec3594413bd2a Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Fri, 23 Oct 2020 13:53:23 +0800 Subject: [PATCH 13/21] restore UpdateCheckMarkSeen --- packages/update_checker/lib/widgets/update_check.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/update_checker/lib/widgets/update_check.dart b/packages/update_checker/lib/widgets/update_check.dart index 031f4089c..235f857a4 100644 --- a/packages/update_checker/lib/widgets/update_check.dart +++ b/packages/update_checker/lib/widgets/update_check.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:update_checker/update_bloc.dart'; import 'package:update_checker/update_info.dart'; import 'package:update_checker/update_state.dart'; +import 'package:update_checker/update_event.dart'; /// UpdateCheck helps in easily integrating logic for checking Updates. /// You can simply extend this class and override [showOptionalAlert] and @@ -33,6 +34,8 @@ class UpdateCheck extends StatelessWidget { } else { showMandatoryPage(context, info); } + var bloc = BlocProvider.of(context); + bloc.add(UpdateCheckMarkSeen()); }); } return child; From ab88c74535facc1cac747f859c5c85dd105067a5 Mon Sep 17 00:00:00 2001 From: Suyash Mohan Date: Mon, 26 Oct 2020 18:09:47 +0800 Subject: [PATCH 14/21] add UpdateCheckDismiss --- packages/update_checker/lib/update_bloc.dart | 2 ++ packages/update_checker/lib/update_event.dart | 9 +++++++++ packages/update_checker/lib/update_state.dart | 9 +++++++++ 3 files changed, 20 insertions(+) diff --git a/packages/update_checker/lib/update_bloc.dart b/packages/update_checker/lib/update_bloc.dart index 7ce88b2ec..8db4fb48a 100644 --- a/packages/update_checker/lib/update_bloc.dart +++ b/packages/update_checker/lib/update_bloc.dart @@ -38,6 +38,8 @@ class UpdateCheckBloc extends Bloc { yield UpdateCancelled(); } else if (event is UpdateCheckMarkSeen) { yield UpdateSeen(); + } else if (event is UpdateCheckMarkDismiss) { + yield UpdateDismissed(); } } } diff --git a/packages/update_checker/lib/update_event.dart b/packages/update_checker/lib/update_event.dart index cb8274a15..a7f979c0a 100644 --- a/packages/update_checker/lib/update_event.dart +++ b/packages/update_checker/lib/update_event.dart @@ -37,3 +37,12 @@ class UpdateCheckMarkSeen extends UpdateCheckEvent { return 'UpdateCheckEvent(UpdateCheckMarkSeen)'; } } + +/// Mark Update alert as dismissed by user +class UpdateCheckMarkDismiss extends UpdateCheckEvent { + @override + String toString() { + return 'UpdateCheckEvent(UpdateCheckMarkDismiss)'; + } +} + diff --git a/packages/update_checker/lib/update_state.dart b/packages/update_checker/lib/update_state.dart index 2ee5f0abe..51721ae18 100644 --- a/packages/update_checker/lib/update_state.dart +++ b/packages/update_checker/lib/update_state.dart @@ -45,3 +45,12 @@ class UpdateSeen extends UpdateCheckState { return 'UpdateCheckState(UpdateSeen)'; } } + +/// State to represent when Update alert was dismised by user. +class UpdateDismissed extends UpdateCheckState { + @override + String toString() { + return 'UpdateCheckState(UpdateDismissed)'; + } +} + From a2546de231a8c7e1af73f75cc37b80691b89d7a0 Mon Sep 17 00:00:00 2001 From: WAQAS YOUNAS Date: Wed, 8 Nov 2023 11:14:52 +0800 Subject: [PATCH 15/21] push update_checker changes --- .../lib/flutter_update_plugin.dart | 1 - .../lib/src/firebase_utils.dart | 70 ------------------- packages/update_checker/lib/src/misc.dart | 15 ---- packages/update_checker/lib/update_bloc.dart | 45 ------------ packages/update_checker/lib/update_event.dart | 48 ------------- packages/update_checker/lib/update_info.dart | 21 ------ packages/update_checker/lib/update_state.dart | 56 --------------- .../lib/widgets/update_check.dart | 45 ------------ .../test/flutter_update_plugin_test.dart | 66 ----------------- 9 files changed, 367 deletions(-) delete mode 100644 packages/update_checker/lib/flutter_update_plugin.dart delete mode 100644 packages/update_checker/lib/src/firebase_utils.dart delete mode 100644 packages/update_checker/lib/src/misc.dart delete mode 100644 packages/update_checker/lib/update_bloc.dart delete mode 100644 packages/update_checker/lib/update_event.dart delete mode 100644 packages/update_checker/lib/update_info.dart delete mode 100644 packages/update_checker/lib/update_state.dart delete mode 100644 packages/update_checker/lib/widgets/update_check.dart delete mode 100644 packages/update_checker/test/flutter_update_plugin_test.dart diff --git a/packages/update_checker/lib/flutter_update_plugin.dart b/packages/update_checker/lib/flutter_update_plugin.dart deleted file mode 100644 index d00850482..000000000 --- a/packages/update_checker/lib/flutter_update_plugin.dart +++ /dev/null @@ -1 +0,0 @@ -library flutter_update_plugin; diff --git a/packages/update_checker/lib/src/firebase_utils.dart b/packages/update_checker/lib/src/firebase_utils.dart deleted file mode 100644 index 70015aafd..000000000 --- a/packages/update_checker/lib/src/firebase_utils.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'dart:io'; -import 'dart:developer' as dev; -import 'package:firebase_database/firebase_database.dart'; -import 'package:update_checker/update_info.dart'; -import 'package:update_checker/src/misc.dart'; -import 'package:package_info/package_info.dart'; - -/// FirebaseUtils class provides [fetchBuildNumbers] method which can be used -/// to fetch update details from Firebase. -/// -/// Firebase Database update data looks like the following -/// build: -/// android: -/// optional: -/// buildnumer: 24 -/// changelog: 'RFAyUCB2ZXJ....' // Base64 -/// url: 'https://alternative.app.link' -/// mandatory: -/// buildnumber: 18 -/// changelog: 'RFAyUCB2ZXJ....' // Base64 -/// url: 'https://alternative.app.link' -class FirebaseUtils { - /// Fetch the Update details. - Future fetchBuildNumbers() async { - try { - final String platform = Platform.operatingSystem; - final DatabaseReference dbRef = - FirebaseDatabase.instance.reference().child('build').child(platform); - - final buildRemoteInfo = (await dbRef.once()).value; - final buildNumberOptional = buildRemoteInfo['optional']['buildnumber']; - final buildNumberMandatory = buildRemoteInfo['mandatory']['buildnumber']; - - // Get BuildNumber currently running - final packageInfo = await PackageInfo.fromPlatform(); - final buildNumberCurrent = int.tryParse(packageInfo.buildNumber) ?? -1; - - if (buildNumberCurrent > 0 && buildNumberCurrent < buildNumberMandatory) { - // Current build is lower than Minimum Required Build - final String buildAppUrl = buildRemoteInfo['mandatory']['url'] ?? null; - final String changelog = - decodeBase64(buildRemoteInfo['mandatory']['changelog'] ?? ''); - - return UpdateInfo( - buildNumber: buildNumberMandatory, - url: buildAppUrl, - changelog: changelog, - isOptional: false, - ); - } else if (buildNumberCurrent > 0 && - buildNumberCurrent < buildNumberOptional) { - // Optional new build is available - final String buildAppUrl = buildRemoteInfo['optional']['url'] ?? null; - final String changelog = - decodeBase64(buildRemoteInfo['optional']['changelog'] ?? ''); - - return UpdateInfo( - buildNumber: buildNumberOptional, - url: buildAppUrl, - changelog: changelog, - isOptional: true, - ); - } - } catch (err) { - // Something went wrong - dev.log(err.toString()); - } - return null; - } -} diff --git a/packages/update_checker/lib/src/misc.dart b/packages/update_checker/lib/src/misc.dart deleted file mode 100644 index be825842c..000000000 --- a/packages/update_checker/lib/src/misc.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:convert'; - -/// Decodes [str] if it is a Base64, -/// otherwise returns the original value of [str]. -String decodeBase64(String str) { - final Codec stringToBase64 = utf8.fuse(base64); - final RegExp _base64 = RegExp( - r'^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$'); - - if (_base64.hasMatch(str)) { - return stringToBase64.decode(str); - } - - return str; -} diff --git a/packages/update_checker/lib/update_bloc.dart b/packages/update_checker/lib/update_bloc.dart deleted file mode 100644 index 8db4fb48a..000000000 --- a/packages/update_checker/lib/update_bloc.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:update_checker/update_event.dart'; -import 'package:update_checker/update_info.dart'; -import 'package:update_checker/update_state.dart'; -import 'package:update_checker/src/firebase_utils.dart'; - -/// UpdateCheckBloc helps in retrieving Update Information from Firebase. -/// [UpdateCheckStart] event can be used to fetch the Update Information. -/// This event is added by default when UpdateCheckBloc is initialised. -/// Use [UpdateCheckCancel] event to cancel the request. -/// And [UpdateCheckMarkSeen] event when update alert has been shown to user. -class UpdateCheckBloc extends Bloc { - bool _updateCancelled = false; - FirebaseUtils firebaseUtils; - - /// Constructor for [UpdateCheckBloc]. - /// An optional mock [FirebaseUtils] instance can be provided for testing. - /// For normal use don't provide fbUtils, it will be constructed itself. - UpdateCheckBloc({FirebaseUtils fbUtils}) : super(UpdateNotAvailable()) { - this.firebaseUtils = fbUtils ?? FirebaseUtils(); - this.add(UpdateCheckStart()); - } - - @override - Stream mapEventToState(UpdateCheckEvent event) async* { - if (event is UpdateCheckStart) { - _updateCancelled = false; - this.add(UpdateCheckInProgress()); - } else if (event is UpdateCheckInProgress) { - UpdateInfo info = await firebaseUtils.fetchBuildNumbers(); - if (info != null && !_updateCancelled) { - yield UpdateAvailable(info); - } else { - yield UpdateNotAvailable(); - } - } else if (event is UpdateCheckCancel) { - _updateCancelled = true; - yield UpdateCancelled(); - } else if (event is UpdateCheckMarkSeen) { - yield UpdateSeen(); - } else if (event is UpdateCheckMarkDismiss) { - yield UpdateDismissed(); - } - } -} diff --git a/packages/update_checker/lib/update_event.dart b/packages/update_checker/lib/update_event.dart deleted file mode 100644 index a7f979c0a..000000000 --- a/packages/update_checker/lib/update_event.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:equatable/equatable.dart'; - -/// Abstract Event class for being used in UpdateCheckBloc. -abstract class UpdateCheckEvent extends Equatable { - @override - List get props => []; -} - -/// Start checking if Update is available or not. -class UpdateCheckStart extends UpdateCheckEvent { - @override - String toString() { - return 'UpdateCheckEvent(UpdateCheckStart)'; - } -} - -/// Update availability is being checked. -class UpdateCheckInProgress extends UpdateCheckEvent { - @override - String toString() { - return 'UpdateCheckEvent(UpdateCheckInProgress)'; - } -} - -/// Cancel the request to check for update availability. -class UpdateCheckCancel extends UpdateCheckEvent { - @override - String toString() { - return 'UpdateCheckEvent(UpdateCheckCancel)'; - } -} - -/// Mark Update check as seen by user. -class UpdateCheckMarkSeen extends UpdateCheckEvent { - @override - String toString() { - return 'UpdateCheckEvent(UpdateCheckMarkSeen)'; - } -} - -/// Mark Update alert as dismissed by user -class UpdateCheckMarkDismiss extends UpdateCheckEvent { - @override - String toString() { - return 'UpdateCheckEvent(UpdateCheckMarkDismiss)'; - } -} - diff --git a/packages/update_checker/lib/update_info.dart b/packages/update_checker/lib/update_info.dart deleted file mode 100644 index 1a803e7df..000000000 --- a/packages/update_checker/lib/update_info.dart +++ /dev/null @@ -1,21 +0,0 @@ -/// Represents information regarding Updates. -class UpdateInfo { - /// [buildNumber] represents the minimum build required. - final int buildNumber; - - /// [changelog] shows what's new in latest buildnumber. - final String changelog; - - /// [url] can be empty. When available should be used instead of App/Play Store. - final String url; - - /// [isOptional] is used to represent if Update is Optional or Mandatory. - final bool isOptional; - - UpdateInfo({ - this.buildNumber, - this.changelog, - this.url, - this.isOptional, - }); -} diff --git a/packages/update_checker/lib/update_state.dart b/packages/update_checker/lib/update_state.dart deleted file mode 100644 index 51721ae18..000000000 --- a/packages/update_checker/lib/update_state.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:update_checker/update_info.dart'; - -/// Abstract State class for being used in UpdateCheckBloc -abstract class UpdateCheckState extends Equatable { - @override - List get props => []; -} - -/// State to represent when there is no Update Available. -class UpdateNotAvailable extends UpdateCheckState { - @override - String toString() { - return 'UpdateNotAvailable'; - } -} - -/// State to represent when an Update is Available. -class UpdateAvailable extends UpdateCheckState { - final UpdateInfo updateInfo; - - UpdateAvailable(this.updateInfo); - - @override - List get props => [updateInfo]; - - @override - String toString() { - return 'UpdateAvailable_${updateInfo.buildNumber}_${updateInfo.isOptional}'; - } -} - -/// State to represent when Update Check was cancelled. -class UpdateCancelled extends UpdateCheckState { - @override - String toString() { - return 'UpdateCheckState(UpdateCancelled)'; - } -} - -/// State to represent when Update alert was seen by user. -class UpdateSeen extends UpdateCheckState { - @override - String toString() { - return 'UpdateCheckState(UpdateSeen)'; - } -} - -/// State to represent when Update alert was dismised by user. -class UpdateDismissed extends UpdateCheckState { - @override - String toString() { - return 'UpdateCheckState(UpdateDismissed)'; - } -} - diff --git a/packages/update_checker/lib/widgets/update_check.dart b/packages/update_checker/lib/widgets/update_check.dart deleted file mode 100644 index 235f857a4..000000000 --- a/packages/update_checker/lib/widgets/update_check.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:update_checker/update_bloc.dart'; -import 'package:update_checker/update_info.dart'; -import 'package:update_checker/update_state.dart'; -import 'package:update_checker/update_event.dart'; - -/// UpdateCheck helps in easily integrating logic for checking Updates. -/// You can simply extend this class and override [showOptionalAlert] and -/// [showMandatoryPage] methods. The extended widget can be inserted to -/// show alert anywhere in the app. -class UpdateCheck extends StatelessWidget { - /// [child] widget that should show the update alert dialog. - final Widget child; - - UpdateCheck({this.child}); - - /// Use [showOptionalAlert] for showing Optional Alerts. - void showOptionalAlert(BuildContext context, UpdateInfo update) {} - - /// Use [showMandatoryPage] for mandatory updates. - void showMandatoryPage(BuildContext context, UpdateInfo update) {} - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is UpdateAvailable) { - UpdateInfo info = state.updateInfo; - // Returning future to run Task after Rendering is done - Future.microtask(() { - if (info.isOptional) { - showOptionalAlert(context, info); - } else { - showMandatoryPage(context, info); - } - var bloc = BlocProvider.of(context); - bloc.add(UpdateCheckMarkSeen()); - }); - } - return child; - }, - ); - } -} diff --git a/packages/update_checker/test/flutter_update_plugin_test.dart b/packages/update_checker/test/flutter_update_plugin_test.dart deleted file mode 100644 index b8c362ac3..000000000 --- a/packages/update_checker/test/flutter_update_plugin_test.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:update_checker/update_bloc.dart'; -import 'package:update_checker/update_event.dart'; -import 'package:update_checker/update_info.dart'; -import 'package:update_checker/update_state.dart'; -import 'package:update_checker/src/firebase_utils.dart'; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; -import 'package:bloc_test/bloc_test.dart'; - -class MockFirebaseUtils extends Mock implements FirebaseUtils {} - -void main() { - WidgetsFlutterBinding.ensureInitialized(); - - group('Update Bloc Test Cases', () { - MockFirebaseUtils fbUtils; - - var updateInfo = - UpdateInfo(buildNumber: 1, changelog: '', url: '', isOptional: false); - var updateInfo2 = - UpdateInfo(buildNumber: 2, changelog: '', url: '', isOptional: true); - - setUp(() { - fbUtils = MockFirebaseUtils(); - }); - - test('Initial State', () { - var updateCheckBloc = UpdateCheckBloc(fbUtils: fbUtils); - expect(updateCheckBloc.state, UpdateNotAvailable()); - }); - - blocTest('Update not available', - build: () { - when(fbUtils.fetchBuildNumbers()).thenAnswer((_) { - return null; - }); - return UpdateCheckBloc(fbUtils: fbUtils); - }, - act: (bloc) => bloc.add(UpdateCheckStart()), - skip: 0, - expect: [UpdateNotAvailable()]); - - blocTest('Update available', - build: () { - when(fbUtils.fetchBuildNumbers()).thenAnswer((_) async { - return updateInfo; - }); - return UpdateCheckBloc(fbUtils: fbUtils); - }, - act: (bloc) => bloc.add(UpdateCheckStart()), - expect: [UpdateAvailable(updateInfo)]); - - blocTest( - 'Update available ', - build: () { - when(fbUtils.fetchBuildNumbers()).thenAnswer((_) async { - return updateInfo2; - }); - return UpdateCheckBloc(fbUtils: fbUtils); - }, - act: (bloc) => bloc.add(UpdateCheckStart()), - expect: [UpdateAvailable(updateInfo2)], - ); - }); -} From a7dc92326f2b61cba888c014b7c34155e9541fe2 Mon Sep 17 00:00:00 2001 From: WAQAS YOUNAS Date: Wed, 8 Nov 2023 13:16:43 +0800 Subject: [PATCH 16/21] Update web_socket_test.dart --- .../test/web_socket/web_socket_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/deriv_web_socket_client/test/web_socket/web_socket_test.dart b/packages/deriv_web_socket_client/test/web_socket/web_socket_test.dart index b19a1d15f..8ba66608a 100644 --- a/packages/deriv_web_socket_client/test/web_socket/web_socket_test.dart +++ b/packages/deriv_web_socket_client/test/web_socket/web_socket_test.dart @@ -262,7 +262,7 @@ void main() { ..send('ping') ..send('pong'); - await Future.delayed(Duration.zero); + await Future.delayed(const Duration(milliseconds: 500)); expect(messages, equals(['ping', 'pong'])); From 26e65dff7c2fe744bc930b724de3f085d83afe1e Mon Sep 17 00:00:00 2001 From: Ahrar Date: Thu, 9 Nov 2023 17:53:47 +0800 Subject: [PATCH 17/21] chore: add pull request template with git rules --- .github/GIT_RULES.md | 129 +++++++++++++++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 55 +++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 .github/GIT_RULES.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/GIT_RULES.md b/.github/GIT_RULES.md new file mode 100644 index 000000000..b21bd8c41 --- /dev/null +++ b/.github/GIT_RULES.md @@ -0,0 +1,129 @@ +## Commit Rules: + +This commits rules is set to ensure all the developers follows a uniform way of writing commits so that it is easy to read the changes made and also automate versioning. +The commits are based on [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) which is a widely followed convention for commits. While writing a commit message to any changes made to code base, make sure it reflects the changes and follows the conventions i.e: + +``` +change_type(package_name): short description/subject line... + +Long Description... +``` + +Here, + +change_type means the type of changes like `feat`,`fix` and so on. + +package_name means which packages this changes is for. This is optional. + +short discription/subject line means about the changes + +Long Description are optional. Add it if its required to reflect the changes in more detail. + +For eg: + +Lets say the changes is about implementing a new feature which is about new social login mechanism in `deriv_auth` package. + +``` +feat(deriv_auth): add UI for sign in page + +- create reusable text field using global app theme +- add bloc for sign in logic implementation +- add google services for authentication +``` + +More changes types: + +| Changes Types | Meaning | Description | +| ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------- | +| feat | Features | A new feature | +| fix | Bug Fixes | A bug fix | +| chore | Chores | Other changes that don’t modify src or test files | +| docs | Docume­ntation | Docume­ntation only changes | +| revert | Reverts | Reverts a previous commit | +| refactor | Code Refact­oring | A code change that neither fixes a bug nor adds a feature | +| test | Tests | Adding missing tests or correcting existing tests | +| style | Styles | Changes that do not affect the meaning of the code (white­ -space, format­ting, missing semi-c­olons, etc) | +| perf | Perfor­mance Improv­ements | A code change that improves perfor­mance | +| build | Builds | Changes that affect the build system or external depend­encies (example scopes: gulp, broccoli, npm) | +| ci | Continuous Integr­ations | Changes to our CI config­uration files and scripts (example scopes: Travis, Circle, Browse­rStack, SauceLabs) | + +Optional: If you would like to pre populate your commit box with the commit template then you can do it by adding `.gitmessage` inside `.github` folder to commit template for that project with the following command.(Note: This won't work if you write commit via terminal.) + +`git config commit.template "YOUR_PROJECT_PATH/.github/.gitmessage"` + +## PR Rules: + +This Rules is set to create a uniform way of submitting Pull requests where all the necessary information for the changes are listed in the title, or description. There is a standard template for creating PR. When you are creating a PR to any repo always make sure: + +- you have titled it following conventional pattern and also included all the necessary information in it. + for eg: + Title: `feat(deriv_auth): [MOBC-299] Add ability to sign in with google` + + Here,
The title defines: + + - what type the changes/PR is about like feature, bug fixes, documentations, refactor.
+ - which package/app is this PR for.(optional)
+ - the clickup card id - short title that describes the changes
+ +- you have added detailed description that gives enough information about the PR. +- you have commits only relevant to your changes and not other's commit which shouldn't be there. + + Also, it is always better to squash commits that are only relevant to any particular PR. Such as changes requested through PR review. If these changes does not reflects any really meaning or value in the main project history then it is always better to squash such commits into one. It helps to maintain a clean commit history. + + **The template looks something like this:** + + + + **Clickup link:** + **Fixes issue:** # + + This PR contains the following changes: + + + + - [ ] ✨ New feature (non-breaking change which adds functionality) + - [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) + - [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) + - [ ] 🧹 Code refactor + - [ ] ✅ Build configuration change + - [ ] 📝 Documentation + - [ ] 🗑️ Chore + + ### Developers Note (Optional) + + + + ## Pre-launch Checklist (For PR creator) + + As a creator of this PR: + + + + - [ ] ✍️ I have included clickup id and package/app_name in the PR title. + - [ ] 👁️ I have gone through the code and removed any temporary changes (commented lines, prints, debug statements etc.). + - [ ] ⚒️ I have fixed any errors/warnings shown by the analyzer/linter. + - [ ] 📝 I have added documentation, comments and logging wherever required. + - [ ] 🧪 I have added necessary tests for these changes. + - [ ] 🔎 I have ensured all existing tests are passing. + + ## Reviewers + + + + ## Pre-launch Checklist (For Reviewers) + + As a reviewer I ensure that: + + - [ ] ✴️ This PR follows the standard PR template. + - [ ] ✴️ The information in this PR properly reflects the code changes. + - [ ] 🧪 All the necessary tests for this PR's are passing. + + ## Pre-launch Checklist (For QA) + + - [ ] 👌 It passes the acceptance criteria. + + ## Pre-launch Checklist (For Maintainer) + + - [ ] [MAINTAINER_NAME] I make sure this PR fulfills its purpose. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..e708d44da --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,55 @@ + + +**Clickup link:** +**Fixes issue:** # + +This PR contains the following changes: + + + +- [ ] ✨ New feature (non-breaking change which adds functionality) +- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) +- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) +- [ ] 🧹 Code refactor +- [ ] ✅ Build configuration change +- [ ] 📝 Documentation +- [ ] 🗑️ Chore + +### Developers Note (Optional) + + + +## Pre-launch Checklist (For PR creator) + +As a creator of this PR: + + + +- [ ] ✍️ I have included clickup id and package/app_name in the PR title. +- [ ] 👁️ I have gone through the code and removed any temporary changes (commented lines, prints, debug statements etc.). +- [ ] ⚒️ I have fixed any errors/warnings shown by the analyzer/linter. +- [ ] 📝 I have added documentation, comments and logging wherever required. +- [ ] 🧪 I have added necessary tests for these changes. +- [ ] 🔎 I have ensured all existing tests are passing. + +## Reviewers + + + +## Pre-launch Checklist (For Reviewers) + +As a reviewer I ensure that: + +- [ ] ✴️ This PR follows the standard PR template. +- [ ] 🪩 The information in this PR properly reflects the code changes. +- [ ] 🧪 All the necessary tests for this PR's are passing. + +## Pre-launch Checklist (For QA) + +- [ ] 👌 It passes the acceptance criteria. + +## Pre-launch Checklist (For Maintainer) + +- [ ] [MAINTAINER_NAME] I make sure this PR fulfills its purpose. From 361f8923e6e582690c220f204ec64c63169b1c5c Mon Sep 17 00:00:00 2001 From: Ahrar Date: Fri, 10 Nov 2023 14:31:01 +0800 Subject: [PATCH 18/21] chore: pull request update git_rules.md --- .github/GIT_RULES.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/.github/GIT_RULES.md b/.github/GIT_RULES.md index b21bd8c41..2307dd09e 100644 --- a/.github/GIT_RULES.md +++ b/.github/GIT_RULES.md @@ -30,6 +30,9 @@ feat(deriv_auth): add UI for sign in page - add bloc for sign in logic implementation - add google services for authentication ``` +### Breaking Changes Indicator +Breaking changes should be indicated by an `!` before the `:` in the subject line e.g. `feat(api)!: remove status endpoint` +* Is an **optional** part of the format More changes types: @@ -51,6 +54,46 @@ Optional: If you would like to pre populate your commit box with the commit temp `git config commit.template "YOUR_PROJECT_PATH/.github/.gitmessage"` +### Examples +* ``` + feat: add email notifications on new direct messages + ``` +* ``` + feat(shopping cart): add the amazing button + ``` +* ``` + feat!: remove ticket list endpoint + + refers to JIRA-1337 + + BREAKING CHANGES: ticket enpoints no longer supports list all entites. + ``` +* ``` + fix(api): handle empty message in request body + ``` +* ``` + fix(api): fix wrong calculation of request body checksum + ``` +* ``` + fix: add missing parameter to service call + + The error occurred because of . + ``` +* ``` + perf: decrease memory footprint for determine uniqe visitors by using HyperLogLog + ``` +* ``` + build: update dependencies + ``` +* ``` + build(release): `bump version to 1.0.0 + ``` +* ``` + refactor: implement fibonacci number calculation as recursion + ``` +* ``` + style: remove empty line + ``` ## PR Rules: This Rules is set to create a uniform way of submitting Pull requests where all the necessary information for the changes are listed in the title, or description. There is a standard template for creating PR. When you are creating a PR to any repo always make sure: From 94bddd42578f859514092ef5bc04c9a6b00614ac Mon Sep 17 00:00:00 2001 From: Ahrar <98078754+ahrar-deriv@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:25:39 +0800 Subject: [PATCH 19/21] chore: merge dev to master (#358) * deps: update deriv_auth_ui dependencies * deps: update deriv_theme in deriv_ui * wip: auth-ui-update * added invalidresidence errror mapping for social login * feat: add deriv auth error state handler * feat: add auth error state handler in sign up page * feat: add auth error state handler in set password page * refactor: auth state handling in signup and set password page * feat: add social button flag * feat: country consent added in country_selection_layout * deps: remove relative path * refactor: auth state handler * test: country selection cubit test * test: auth state handler * feat: add gesture detector in get started layout * feat: reset pass success page * docs: update documentation based on latest changes * refactor: add eof and remove deprecated lint * refactor: make countries non-nullable * refactor: deriv country selection cubit * refactor: deriv country selection cubit * ci: integrate github actions and setup melos (#328) * chore: rename `AuthStateListener` * fix: change color to general * fix: reset password button alignment * fix: add missing ok label in referal dialog * feat: add flag for social login * fix: spacing after social flag * chore: add auth ui regex to deriv ui * feat: [MOBC-608] auth ui setting page (#320) * feat: setting page * fix: depricated issues * fix: change package_info_plus to match packages * fix: future void callback * refactor: move regex_helper func to deriv_ui * fix: remove sahani repo from yaml files * fix: deriv_auth_ui pubspec * fix: remove appEnv related to deriv ez as deriv ez is removed from production we dont need to include it in this package anymore * fix: revert back the appEnv and change the comment * chore: merge master to dev (#334) * add update_checker package * add Readme * add comments * update documentation * apply suggestions from PR review * update deps * update flutter_bloc package * Revert "update flutter_bloc package" This reverts commit 7188a184363fafe0601f0fa904a3f42937e40978. * Update packages/update_checker/lib/update_info.dart Co-authored-by: abed-binary <61964755+abed-binary@users.noreply.github.com> * update bloc library to 6.0.1 * remove markseen * remove unnecessary statements * restore UpdateCheckMarkSeen * add UpdateCheckDismiss * push update_checker changes * Update web_socket_test.dart * chore: add pull request template with git rules * chore: pull request update git_rules.md --------- Co-authored-by: Suyash Mohan Co-authored-by: Suyash Mohan <33442325+suyash-binary@users.noreply.github.com> Co-authored-by: abed-binary <61964755+abed-binary@users.noreply.github.com> Co-authored-by: Morteza Tavanarad Co-authored-by: ramin-deriv Co-authored-by: ramin-deriv <55975218+ramin-deriv@users.noreply.github.com> Co-authored-by: WAQAS YOUNAS Co-authored-by: WAQAS YOUNAS <78855520+waqas-younas-deriv@users.noreply.github.com> * add invalidate and copyValuesFrom to AbstractEMAIndicator (#331) * ci: automated versioning github actions and updated ref (#338) * deps: update dependencies update dependencies deriv_ui update dependencies part 1 update dependencies deriv_auth_ui * deps: push melos pubspec.lock * chore: publish packages chore(release): publish packages - analytics@1.0.0 - deriv_app_performance@0.0.1 - flutter_deriv_bloc_manager@0.0.1 - deriv_datadog@0.0.1 - deriv_dependency_injector@1.0.0 - deriv_env@0.0.1 - deriv_grouped_listview@0.0.1 - deriv_http_client@1.0.0 - deriv_lint@1.0.0 - deriv_rudderstack@1.1.0 - deriv_technical_analysis@0.0.1 - deriv_theme@2.0.0 - deriv_web_socket_client@1.0.0 - form_builder@1.0.0+1 - update_checker@1.1.0 chore(release): publish packages - deriv_auth@1.0.2 - deriv_banner@0.0.1 - deriv_date_range_picker@0.0.1 - deriv_expandable_bottom_sheet@0.0.1 - deriv_live_chat@0.0.1 - deriv_numpad@1.0.0+1 - deriv_store_launcher@0.0.1 - deriv_web_view@0.0.1 chore(release): publish packages - deriv_ui@0.0.1 chore(release): publish packages - deriv_auth_ui@0.0.1 * chore: clean changelog * chore: revert changelog changes * chore: change auth ui example app ref * ci: git actions for versioning * refactor(deriv_env): make package independent of env file (#318) * get the env file content from outside * make Env class not being singleton * create EnvLoader class * fix the tests * bring back the removed test case * update the initialize doc * export classes need to be used by outside * export BaseEnv class as well * format deriv_env.dart * swape names for Env and EnvLoader to reduce the amount of change in the apps using this packagew * handle exception in bool type * remove duplicate lint rules * define common values as variable in env_test.dart * update README.md file * remove breaking change by making the laod method deprecated * Update CHANGELOG.md * udpate README.md file * refactor: Improve flexibility by removing dependency to env file - **Deprecation:** The `load` method is deprecated; it is recommended to use the new `initialize` method for improved flexibility. The `load` method will still function but may be removed in future releases. - Introduced a new method `initialize` to handle initialization without requiring an env file directly. - Improved package flexibility by removing the strict dependency on an env file for test environments. * chore: [MOBC-632] remove commit hash ci (#340) * chore(version): bump version and update changelog (#342) - deriv_env@0.0.1+1 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * chore(deriv_auth_ui): remove unecessary deps from example (#343) * feat: Ayaan/Added margins 112 and 164 (#319) * Add dimens 112 and 164 * Update dimens.dart * chore(release): publish packages (#344) - deriv_theme@2.1.0 - deriv_date_range_picker@0.0.1+1 - deriv_ui@0.0.1+1 - deriv_auth_ui@0.0.1+1 - deriv_expandable_bottom_sheet@0.0.1+1 - deriv_numpad@1.0.1 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * refactor(deriv_auth_ui): [MOBC-629] Adding semantics to UI components (#321) * chore(deriv_auth_ui):Adding semantics to UI components * chore(deriv_auth_ui):Adding semantics to UI components, fixes based on review comments * chore(deriv_auth_ui):Adding explicitChildNodes: true to Semantics Widgets * chore(release): publish packages (#345) - deriv_auth_ui@0.0.1+2 - deriv_ui@0.0.1+2 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * docs: add semantic versioning to git rules doc (#346) * docs: add semantic versioning document * docs: styling the git rules md file * docs: GIT_RULES style * chore: Update .github/GIT_RULES.md add refactor Co-authored-by: sahani-deriv <125638269+sahani-deriv@users.noreply.github.com> * chore: add missing format GIT_RULES.md --------- Co-authored-by: sahani-deriv <125638269+sahani-deriv@users.noreply.github.com> * feat(deriv_auth): [DERG-1299] add user agent to login (#341) * feat(deriv_auth): add user agent add optional user agent for login requests * feat(deriv_auth)add user agent to get user tokens request header * fix(deriv_auth): User-Agent typo * fix(deriv_auth): add missing parameter * fix(deriv_auth): add default value for user agent parameter --------- Co-authored-by: Ahrar * chore(release): publish packages (#348) - deriv_auth@1.1.0 - deriv_auth_ui@0.0.1+3 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * add remote config source to update checker * update key * update version rename base firebase class checking platform type * fixing decode changelogs data. * fixing change logs issue * fixing change logs issue * add fetchAndActivate to remote config * add fetchAndActivate to remote config * add fetchAndActivate to remote config * add huaweiUrl to the UpdateInfo model to redirecting users to app gallery. * fix comments * fix comments * chore:bum up version (#351) * refactor(analytics): [MOBC-546] Creating unified analytics package. (#315) * Refactoring `analytics` package. * PR review comments on `analytics` package * added `setUserProperty` function to `DerivFirebaseAnalytics` * fixed a test case * EOF `.env.example` in `analytics` package * chore(analytics): fixed package version. * chore(analytics): added `logEvent` function to DerivDatadog * docs(analytics): corrected `setup` function call for datadog * chore(analytics): added `.env` file to `.gitignore` * refactor(analytics): added `debugEnabled` to `RudderstackConfiguration` * docs(analytics): added debugEnabled to RudderstackConfiguration * revert(analytics): versioning and CHANGELOG - Introduced a flexible configuration system to support multiple analytics providers. - Added `BaseAnalyticsConfiguration` interface for standardizing analytics configuration. - Internalized and restructured Datadog analytics by providing a `DerivDatadog` class that implements `BaseAnalytics`. - Refined existing analytics services to have their own classes implementing `BaseAnalytics`. - Introduced Enum types and extensions (`TrackingConsent`, `DatadogSite`) to aid in analytics configuration. - Improved navigation observer support to work with multiple analytics providers. - Enhanced exception handling to capture Flutter and Platform-specific errors. - Added ability to set user information for analytics tracking. - Replaced in-house Rudderstack plugin with the official Rudderstack SDK. ### Breaking changes: **General**: - `setup()` method for analytics providers now requires configuration classes implementing `BaseAnalyticsConfiguration`. - Exception handling updated to include multiple analytics providers. - Rudderstack: Now requires the official Rudderstack SDK for analytics. Update dependencies accordingly. - Datadog: Migrated from a separate package to an internal implementation within this package. - Existing analytics services have been restructured to implement `BaseAnalytics`, potentially changing function signatures and configurations. * chore(release): publish packages (#356) - analytics@1.0.1 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: sahani-deriv <125638269+sahani-deriv@users.noreply.github.com> Co-authored-by: Istiak Co-authored-by: Suyash Mohan Co-authored-by: Suyash Mohan <33442325+suyash-binary@users.noreply.github.com> Co-authored-by: abed-binary <61964755+abed-binary@users.noreply.github.com> Co-authored-by: Morteza Tavanarad Co-authored-by: ramin-deriv Co-authored-by: ramin-deriv <55975218+ramin-deriv@users.noreply.github.com> Co-authored-by: WAQAS YOUNAS Co-authored-by: WAQAS YOUNAS <78855520+waqas-younas-deriv@users.noreply.github.com> Co-authored-by: mobile-apps-deriv <134251399+mobile-apps-deriv@users.noreply.github.com> Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: ayaan-deriv <142789997+ayaan-deriv@users.noreply.github.com> Co-authored-by: ernest-deriv <120568427+ernest-deriv@users.noreply.github.com> Co-authored-by: zohreh <108648307+zohreh-deriv@users.noreply.github.com> Co-authored-by: Reza <94842463+Reza-deriv@users.noreply.github.com> Co-authored-by: Bassam El Obeid <127500305+bassam-deriv@users.noreply.github.com> Co-authored-by: Bassam El Obeid --- .github/GIT_RULES.md | 58 +- .github/workflows/all_packages.yaml | 44 ++ .github/workflows/pr_title.yaml | 19 + .github/workflows/version.yaml | 55 ++ .gitignore | 3 +- CHANGELOG.md | 137 +++++ README.md | 37 ++ melos.yaml | 42 ++ packages/analytics/CHANGELOG.md | 5 + packages/analytics/README.md | 200 ++++--- packages/analytics/example/.env.example | 9 + packages/analytics/example/.gitignore | 48 ++ packages/analytics/example/.metadata | 10 + packages/analytics/example/README.md | 9 + .../analytics/example/analysis_options.yaml | 29 + packages/analytics/example/android/.gitignore | 13 + .../example/android/app/build.gradle | 68 +++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 34 ++ .../com/example/example/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../analytics/example/android/build.gradle | 31 + .../example/android/gradle.properties | 3 + .../analytics/example/android/settings.gradle | 11 + packages/analytics/example/ios/.gitignore | 34 ++ .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../example/ios/Flutter/Debug.xcconfig | 2 + .../example/ios/Flutter/Release.xcconfig | 2 + packages/analytics/example/ios/Podfile | 50 ++ packages/analytics/example/ios/Podfile.lock | 178 ++++++ .../ios/Runner.xcodeproj/project.pbxproj | 552 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 87 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 ++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../analytics/example/ios/Runner/Info.plist | 51 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + packages/analytics/example/lib/main.dart | 83 +++ .../example/lib/pages/first_page.dart | 28 + .../example/lib/pages/rudderstack.dart | 175 ++++++ .../example/lib/pages/second_page.dart | 54 ++ .../example/lib/pages/splash_screen.dart | 36 ++ packages/analytics/example/pubspec.yaml | 38 ++ packages/analytics/lib/analytics.dart | 84 +-- packages/analytics/lib/core/logger.dart | 15 + .../analytics/lib/sdk/base_analytics.dart | 11 + .../lib/sdk/base_analytics_configuration.dart | 2 + .../datadog/core/datadog_configuration.dart | 45 ++ .../analytics/lib/sdk/datadog/core/enums.dart | 108 ++++ .../lib/sdk/datadog/sdk/deriv_datadog.dart | 109 ++++ .../firebase/core/firebase_configuration.dart | 12 + .../sdk/deriv_firebase_analytics.dart | 105 ++++ .../core/rudderstack_configuration.dart | 20 + .../sdk/deriv_rudderstack_sdk.dart | 109 ++++ packages/analytics/pubspec.yaml | 16 +- .../deriv_datadog_configuration_test.dart | 31 + .../test/datadog/core/enums_test.dart | 27 + .../test/datadog/sdk/deriv_datadog_test.dart | 47 ++ ...firebase_analytics_configuration_test.dart | 20 + .../firebase/sdk/deriv_firebase_sdk_test.dart | 107 ++++ .../deriv_rudderstack_configuration_test.dart | 18 + .../sdk/deriv_rudderstack_sdk_test.dart | 142 +++++ packages/deriv_auth/CHANGELOG.md | 4 + .../token/services/base_token_service.dart | 1 + .../token/services/deriv_token_service.dart | 6 +- .../features/auth/cubit/deriv_auth_cubit.dart | 36 +- .../auth/services/base_auth_service.dart | 7 +- .../auth/services/deriv_auth_service.dart | 12 +- packages/deriv_auth/pubspec.yaml | 4 +- .../auth/cubit/deriv_auth_cubit_test.dart | 38 +- .../auth/service/deriv_auth_service_test.dart | 31 +- packages/deriv_auth_ui/CHANGELOG.md | 12 + packages/deriv_auth_ui/README.md | 132 +++++ packages/deriv_auth_ui/analysis_options.yaml | 4 +- .../example_auth_error_state_handler.dart | 16 + .../get_started/pages/get_started_page.dart | 1 + .../lib/features/login/pages/login_page.dart | 2 + .../pages/choose_new_password_page.dart | 7 +- .../pages/reset_pass_success_page.dart | 51 ++ .../signup/pages/set_password_page.dart | 13 +- .../features/signup/pages/signup_page.dart | 2 + packages/deriv_auth_ui/example/pubspec.yaml | 11 - packages/deriv_auth_ui/lib/deriv_auth_ui.dart | 3 + .../lib/generated/intl/messages_en.dart | 41 ++ .../deriv_auth_ui/lib/generated/l10n.dart | 230 ++++++++ packages/deriv_auth_ui/lib/l10n/intl_en.arb | 27 +- .../helpers/country_selection_helper.dart | 86 +++ .../lib/src/core/helpers/endpoint_helper.dart | 36 ++ .../lib/src/core/helpers/semantic_labels.dart | 41 ++ .../core/states/auth_error_state_handler.dart | 127 ++++ .../core/states/auth_error_state_mapper.dart | 51 ++ .../src/core/states/auth_state_listener.dart | 63 ++ .../lib/src/core/states/states.dart | 2 + .../layouts/deriv_get_started_layout.dart | 51 +- .../login/layouts/deriv_2fa_layout.dart | 2 +- .../login/layouts/deriv_login_layout.dart | 38 +- .../widgets/deriv_social_auth_divider.dart | 36 +- .../widgets/deriv_social_auth_panel.dart | 26 +- .../layouts/deriv_choose_new_pass_layout.dart | 61 +- .../layouts/deriv_reset_pass_layout.dart | 54 +- .../deriv_success_pass_change_layout.dart | 59 ++ .../layouts/deriv_setting_layout.dart | 210 +++++++ .../cubits/deriv_country_selection_cubit.dart | 77 +-- .../cubits/deriv_country_selection_state.dart | 76 ++- .../deriv_country_selection_layout.dart | 168 +++++- .../deriv_email_not_received_layout.dart | 10 +- .../layouts/deriv_set_password_layout.dart | 19 +- .../signup/layouts/deriv_signup_layout.dart | 138 +++-- .../deriv_verification_done_layout.dart | 2 +- .../layouts/deriv_verify_email_layout.dart | 2 +- .../models/deriv_password_policy_model.dart | 1 - .../country_selection_list_widget.dart | 4 +- .../password_policy_checker_widget.dart | 6 +- packages/deriv_auth_ui/pubspec.yaml | 21 +- ...deriv_unavailable_country_layout_test.dart | 5 +- .../states/auth_error_state_mapper_test.dart | 206 +++++++ .../core/states/auth_state_listener_test.dart | 134 +++++ .../deriv_get_started_layout_test.dart | 13 +- .../login/layouts/deriv_2fa_layout_test.dart | 16 +- .../layouts/deriv_login_layout_test.dart | 74 ++- .../deriv_choose_new_pass_layout_test.dart | 13 +- .../layouts/deriv_reset_pass_layout_test.dart | 17 +- ...deriv_success_pass_change_layout_test.dart | 17 + .../deriv_country_selection_cubit_test.dart | 88 ++- .../deriv_country_selection_state_test.dart | 39 -- .../deriv_country_selection_layout_test.dart | 8 +- .../deriv_email_not_received_layout_test.dart | 8 +- .../deriv_set_password_layout_test.dart | 104 ++-- .../layouts/deriv_signup_layout_test.dart | 227 ++++--- .../deriv_verification_done_layout_test.dart | 7 +- .../deriv_verify_email_layout_test.dart | 8 +- .../models/deriv_auth_utm_model_test.dart | 9 +- .../deriv_password_policy_model_test.dart | 13 +- .../models/deriv_residence_model_test.dart | 4 +- .../country_selection_list_widget_test.dart | 12 +- packages/deriv_auth_ui/test/pump_app.dart | 2 +- packages/deriv_banner/example/pubspec.yaml | 2 +- packages/deriv_banner/pubspec.yaml | 2 +- packages/deriv_bloc_manager/README.md | 6 +- packages/deriv_date_range_picker/CHANGELOG.md | 4 + .../lib/src/date_range_picker.dart | 2 +- .../src/widgets/date_range_text_field.dart | 2 +- .../lib/src/widgets/input_date_range.dart | 2 +- .../lib/src/widgets/month_item.dart | 4 +- packages/deriv_date_range_picker/pubspec.yaml | 4 +- packages/deriv_env/CHANGELOG.md | 4 + packages/deriv_env/README.md | 6 +- packages/deriv_env/analysis_options.yaml | 3 +- .../{base_env.dart => base_env_loader.dart} | 15 +- packages/deriv_env/lib/deriv_env.dart | 3 + packages/deriv_env/lib/env.dart | 138 ++--- packages/deriv_env/lib/env_loader.dart | 119 ++++ packages/deriv_env/pubspec.yaml | 2 +- packages/deriv_env/test/cipher_test.dart | 2 +- packages/deriv_env/test/env_test.dart | 69 ++- .../CHANGELOG.md | 4 + .../pubspec.yaml | 4 +- packages/deriv_lint/CHANGELOG.md | 2 + packages/deriv_live_chat/pubspec.yaml | 2 +- packages/deriv_numpad/CHANGELOG.md | 4 + .../lib/core/widgets/custom_checkbox.dart | 2 +- packages/deriv_numpad/pubspec.yaml | 4 +- .../deriv_store_launcher/example/pubspec.yaml | 2 +- packages/deriv_store_launcher/pubspec.yaml | 2 +- .../calculations/ema_indicator.dart | 18 + packages/deriv_theme/CHANGELOG.md | 4 + packages/deriv_theme/lib/src/dimens.dart | 6 + packages/deriv_theme/lib/theme_provider.dart | 4 + packages/deriv_theme/pubspec.yaml | 2 +- packages/deriv_ui/CHANGELOG.md | 8 + .../presentation/widgets/base_text_field.dart | 117 ++-- .../presentation/widgets/custom_checkbox.dart | 19 +- .../lib/utils/popup_dialogs_helper.dart | 8 +- .../deriv_ui/lib/utils/regex_helpers.dart | 7 + packages/deriv_ui/pubspec.yaml | 14 +- packages/deriv_web_view/pubspec.yaml | 2 +- .../example/lib/update_bloc_page.dart | 3 +- .../example/lib/update_checker_page.dart | 1 + .../lib/src/bloc/update_bloc.dart | 84 ++- .../lib/src/models/update_info.dart | 5 + .../lib/src/presentation/update_checker.dart | 9 +- .../lib/src/repositories/base_firebase.dart | 6 + .../src/repositories/firebase_database.dart | 4 +- .../repositories/firebase_remote_config.dart | 20 + .../lib/src/repositories/repositories.dart | 1 + packages/update_checker/pubspec.lock | 90 ++- packages/update_checker/pubspec.yaml | 3 +- .../test/update_checker_test.dart | 7 +- pubspec.lock | 317 ++++++++++ pubspec.yaml | 7 + 226 files changed, 6955 insertions(+), 1053 deletions(-) create mode 100644 .github/workflows/all_packages.yaml create mode 100644 .github/workflows/pr_title.yaml create mode 100644 .github/workflows/version.yaml create mode 100644 CHANGELOG.md create mode 100644 melos.yaml create mode 100644 packages/analytics/example/.env.example create mode 100644 packages/analytics/example/.gitignore create mode 100644 packages/analytics/example/.metadata create mode 100644 packages/analytics/example/README.md create mode 100644 packages/analytics/example/analysis_options.yaml create mode 100644 packages/analytics/example/android/.gitignore create mode 100644 packages/analytics/example/android/app/build.gradle create mode 100644 packages/analytics/example/android/app/src/debug/AndroidManifest.xml create mode 100644 packages/analytics/example/android/app/src/main/AndroidManifest.xml create mode 100644 packages/analytics/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt create mode 100644 packages/analytics/example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 packages/analytics/example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 packages/analytics/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/analytics/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/analytics/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/analytics/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/analytics/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 packages/analytics/example/android/app/src/main/res/values-night/styles.xml create mode 100644 packages/analytics/example/android/app/src/main/res/values/styles.xml create mode 100644 packages/analytics/example/android/app/src/profile/AndroidManifest.xml create mode 100644 packages/analytics/example/android/build.gradle create mode 100644 packages/analytics/example/android/gradle.properties create mode 100644 packages/analytics/example/android/settings.gradle create mode 100644 packages/analytics/example/ios/.gitignore create mode 100644 packages/analytics/example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 packages/analytics/example/ios/Flutter/Debug.xcconfig create mode 100644 packages/analytics/example/ios/Flutter/Release.xcconfig create mode 100644 packages/analytics/example/ios/Podfile create mode 100644 packages/analytics/example/ios/Podfile.lock create mode 100644 packages/analytics/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/analytics/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/analytics/example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/analytics/example/ios/Runner/AppDelegate.swift create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 packages/analytics/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 packages/analytics/example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 packages/analytics/example/ios/Runner/Info.plist create mode 100644 packages/analytics/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 packages/analytics/example/lib/main.dart create mode 100644 packages/analytics/example/lib/pages/first_page.dart create mode 100644 packages/analytics/example/lib/pages/rudderstack.dart create mode 100644 packages/analytics/example/lib/pages/second_page.dart create mode 100644 packages/analytics/example/lib/pages/splash_screen.dart create mode 100644 packages/analytics/example/pubspec.yaml create mode 100644 packages/analytics/lib/core/logger.dart create mode 100644 packages/analytics/lib/sdk/base_analytics.dart create mode 100644 packages/analytics/lib/sdk/base_analytics_configuration.dart create mode 100644 packages/analytics/lib/sdk/datadog/core/datadog_configuration.dart create mode 100644 packages/analytics/lib/sdk/datadog/core/enums.dart create mode 100644 packages/analytics/lib/sdk/datadog/sdk/deriv_datadog.dart create mode 100644 packages/analytics/lib/sdk/firebase/core/firebase_configuration.dart create mode 100644 packages/analytics/lib/sdk/firebase/sdk/deriv_firebase_analytics.dart create mode 100644 packages/analytics/lib/sdk/rudderstack/core/rudderstack_configuration.dart create mode 100644 packages/analytics/lib/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart create mode 100644 packages/analytics/test/datadog/core/deriv_datadog_configuration_test.dart create mode 100644 packages/analytics/test/datadog/core/enums_test.dart create mode 100644 packages/analytics/test/datadog/sdk/deriv_datadog_test.dart create mode 100644 packages/analytics/test/firebase/core/deriv_firebase_analytics_configuration_test.dart create mode 100644 packages/analytics/test/firebase/sdk/deriv_firebase_sdk_test.dart create mode 100644 packages/analytics/test/rudderstack/core/deriv_rudderstack_configuration_test.dart create mode 100644 packages/analytics/test/rudderstack/sdk/deriv_rudderstack_sdk_test.dart create mode 100644 packages/deriv_auth_ui/example/lib/core/example_auth_error_state_handler.dart create mode 100644 packages/deriv_auth_ui/example/lib/features/reset_pass/pages/reset_pass_success_page.dart create mode 100644 packages/deriv_auth_ui/lib/src/core/helpers/country_selection_helper.dart create mode 100644 packages/deriv_auth_ui/lib/src/core/helpers/endpoint_helper.dart create mode 100644 packages/deriv_auth_ui/lib/src/core/helpers/semantic_labels.dart create mode 100644 packages/deriv_auth_ui/lib/src/core/states/auth_error_state_handler.dart create mode 100644 packages/deriv_auth_ui/lib/src/core/states/auth_error_state_mapper.dart create mode 100644 packages/deriv_auth_ui/lib/src/core/states/auth_state_listener.dart create mode 100644 packages/deriv_auth_ui/lib/src/core/states/states.dart create mode 100644 packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_success_pass_change_layout.dart create mode 100644 packages/deriv_auth_ui/lib/src/features/setting_page/layouts/deriv_setting_layout.dart create mode 100644 packages/deriv_auth_ui/test/core/states/auth_error_state_mapper_test.dart create mode 100644 packages/deriv_auth_ui/test/core/states/auth_state_listener_test.dart create mode 100644 packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_success_pass_change_layout_test.dart delete mode 100644 packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_state_test.dart rename packages/deriv_env/lib/{base_env.dart => base_env_loader.dart} (55%) create mode 100644 packages/deriv_env/lib/deriv_env.dart create mode 100644 packages/deriv_env/lib/env_loader.dart create mode 100644 packages/deriv_lint/CHANGELOG.md create mode 100644 packages/deriv_numpad/CHANGELOG.md create mode 100644 packages/update_checker/lib/src/repositories/base_firebase.dart create mode 100644 packages/update_checker/lib/src/repositories/firebase_remote_config.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml diff --git a/.github/GIT_RULES.md b/.github/GIT_RULES.md index 2307dd09e..584dacbfc 100644 --- a/.github/GIT_RULES.md +++ b/.github/GIT_RULES.md @@ -1,5 +1,4 @@ -## Commit Rules: - +## Commit Rules: This commits rules is set to ensure all the developers follows a uniform way of writing commits so that it is easy to read the changes made and also automate versioning. The commits are based on [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) which is a widely followed convention for commits. While writing a commit message to any changes made to code base, make sure it reflects the changes and follows the conventions i.e: @@ -30,12 +29,7 @@ feat(deriv_auth): add UI for sign in page - add bloc for sign in logic implementation - add google services for authentication ``` -### Breaking Changes Indicator -Breaking changes should be indicated by an `!` before the `:` in the subject line e.g. `feat(api)!: remove status endpoint` -* Is an **optional** part of the format - -More changes types: - +More changes types: | Changes Types | Meaning | Description | | ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------- | | feat | Features | A new feature | @@ -94,7 +88,53 @@ Optional: If you would like to pre populate your commit box with the commit temp * ``` style: remove empty line ``` -## PR Rules: +### Breaking Changes Indicator +Breaking changes should be indicated by an + ! +before the `:` in the subject line e.g. `feat(api)!: remove status endpoint` +* Is an **optional** part of the format + + +

 

+ +## Semantic versioning: +
1 . 4 . 3 + 2
+
Major . Minor . Patch + build
+

 

+ +Major Release: +If a ```“breaking change”``` is introduced, the major release number must be increased + +``` +feat(api)!: remove status endpoint +fix!: bug fix with breaking change +``` + +Minor Release: +New features have been introduced, which are backwards compatible ```no “breaking changes”``` + + +``` +feat: new feature +``` + +Patch Release: +Bug fixes ```no “breaking changes”```
+``` +fix: something in code +refactor: code changes that doesn't fix or add anything +``` + +Build: This number is optional and can be used to differentiate between different builds of the same version. +
+ +no change | | build bump: +``` +build, chore, ci, docs, style, perf, test +``` +

 

+ +## PR Rules: This Rules is set to create a uniform way of submitting Pull requests where all the necessary information for the changes are listed in the title, or description. There is a standard template for creating PR. When you are creating a PR to any repo always make sure: diff --git a/.github/workflows/all_packages.yaml b/.github/workflows/all_packages.yaml new file mode 100644 index 000000000..6e25797c2 --- /dev/null +++ b/.github/workflows/all_packages.yaml @@ -0,0 +1,44 @@ +name: all_packages + +on: + push: + branches: + - master + - dev + pull_request: + branches: + - "*" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + analyze_and_test: + runs-on: ubuntu-latest + steps: + - name: Git Checkout + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + flutter-version: "3.10.2" + cache: true + + - name: Set SSH Key + uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: ${{secrets.SSH_PRIVATE_KEY}} + + - name: Install Melos and run pub get + uses: bluefireteam/melos-action@v1 + with: + melos-version: "3.0.1" + + - name: Run Analyze + run: melos run analyze + + - name: Run Test + run: melos run test --no-select diff --git a/.github/workflows/pr_title.yaml b/.github/workflows/pr_title.yaml new file mode 100644 index 000000000..731c27830 --- /dev/null +++ b/.github/workflows/pr_title.yaml @@ -0,0 +1,19 @@ +name: pr_title + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +permissions: + pull-requests: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/version.yaml b/.github/workflows/version.yaml new file mode 100644 index 000000000..c93910aab --- /dev/null +++ b/.github/workflows/version.yaml @@ -0,0 +1,55 @@ +name: version + +on: + pull_request: + types: + - closed + branches: + - dev + +permissions: + contents: write + pull-requests: write + +jobs: + version_and_tag: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true && !contains(github.event.pull_request.title, 'chore(version)') + steps: + - name: Git Checkout + uses: actions/checkout@v4 + with: + ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} + fetch-depth: 0 + + - name: Setup Git User + uses: fregante/setup-git-user@v2 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + flutter-version: "3.10.2" + cache: true + + - name: Setup Melos + uses: bluefireteam/melos-action@v3 + with: + melos-version: "3.2.0" + run-bootstrap: false + + - name: Create git tag based on version + # only consider changes from the commit mentioned below + run: melos version --all --yes + + - name: Push tag + run: git push --tags + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create Pull Request on updated changelog and pubspec file. + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.PAT }} + title: "chore(version): bump version and update changelog" + base: dev diff --git a/.gitignore b/.gitignore index 6f645b362..986f8f684 100644 --- a/.gitignore +++ b/.gitignore @@ -24,10 +24,11 @@ lib/basic_api/generated/*.json .flutter-plugins .packages .pub-cache/ -pubspec.lock .pub/ /build/ coverage/ +/packages/**/pubspec_overrides.yaml +/packages/**/pubspec.lock gradlew.bat diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..5999f79a0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,137 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## 2023-12-08 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`analytics` - `v1.0.1`](#analytics---v101) + +--- + +#### `analytics` - `v1.0.1` + + - **REVERT**(analytics): versioning and CHANGELOG. ([81a3a0df](https://github.com/regentmarkets/flutter-deriv-packages/commit/81a3a0df27208bd200009415855c6cb944d016e3)) + - **REFACTOR**(analytics): [MOBC-546] Creating unified analytics package. ([#315](https://github.com/regentmarkets/flutter-deriv-packages/issues/315)). ([fd1d8ed3](https://github.com/regentmarkets/flutter-deriv-packages/commit/fd1d8ed345d4ecf91c5f6c1463c5196b40abcbf6)) + + +## 2023-12-04 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v1.1.0`](#deriv_auth---v110) + - [`deriv_auth_ui` - `v0.0.1+3`](#deriv_auth_ui---v0013) + +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_ui` - `v0.0.1+3` + +--- + +#### `deriv_auth` - `v1.1.0` + + - **FEAT**(deriv_auth): [DERG-1299] add user agent to login ([#341](https://github.com/regentmarkets/flutter-deriv-packages/issues/341)). ([37f5b763](https://github.com/regentmarkets/flutter-deriv-packages/commit/37f5b763d8a679ff1d458fc4e9b82578f4eecf83)) + + +## 2023-11-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth_ui` - `v0.0.1+2`](#deriv_auth_ui---v0012) + - [`deriv_ui` - `v0.0.1+2`](#deriv_ui---v0012) + +--- + +#### `deriv_auth_ui` - `v0.0.1+2` + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + +#### `deriv_ui` - `v0.0.1+2` + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + + +## 2023-11-28 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_theme` - `v2.1.0`](#deriv_theme---v210) + - [`deriv_date_range_picker` - `v0.0.1+1`](#deriv_date_range_picker---v0011) + - [`deriv_ui` - `v0.0.1+1`](#deriv_ui---v0011) + - [`deriv_auth_ui` - `v0.0.1+1`](#deriv_auth_ui---v0011) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+1`](#deriv_expandable_bottom_sheet---v0011) + - [`deriv_numpad` - `v1.0.1`](#deriv_numpad---v101) + +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_date_range_picker` - `v0.0.1+1` + - `deriv_ui` - `v0.0.1+1` + - `deriv_auth_ui` - `v0.0.1+1` + - `deriv_expandable_bottom_sheet` - `v0.0.1+1` + - `deriv_numpad` - `v1.0.1` + +--- + +#### `deriv_theme` - `v2.1.0` + + - **FEAT**: Ayaan/Added margins 112 and 164 ([#319](https://github.com/regentmarkets/flutter-deriv-packages/issues/319)). ([bf625820](https://github.com/regentmarkets/flutter-deriv-packages/commit/bf6258206b4bb4cbdd1ef6744e07e2adb8d0d5ee)) + + +## 2023-11-27 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_env` - `v0.0.1+1`](#deriv_env---v0011) + +--- + +#### `deriv_env` - `v0.0.1+1` + + - **REFACTOR**(deriv_env): make package independent of env file ([#318](https://github.com/regentmarkets/flutter-deriv-packages/issues/318)). ([a7242c81](https://github.com/regentmarkets/flutter-deriv-packages/commit/a7242c81b97fda70a622d7bbbb97fe997067117a)) + diff --git a/README.md b/README.md index 5b70334a5..0be380907 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,40 @@ This repository contains private packages & plugins that are used by the company | [form_builder](./packages/form_builder) | A simpler and cleaner way to create, validate and submit forms. | | [update_checker](./packages/update_checker) | Check and retrieve update information from the server for the given package. | +## Environment Setup + +We use [Melos](https://pub.dev/packages/melos) to manage the multiple packages in this repository. To get started, install Melos globally: + +```bash +$ dart pub global activate melos +``` + +Running `pub get` on all packages, run: + +```bash +$ melos bootstrap +``` + +`Analyze` and `Test` has already been configured in `melos.yaml` so you can do the following: + +Running `flutter analyze` on all packages: + +```bash +$ melos run analyze +``` + +Running `flutter test` on all packages: + +```bash +$ melos run test --no-select +``` + +If you'd like to run any other command on all packages, you can configure it in `melos.yaml` or run it directly with melos: + +```bash +$ melos exec --\ + your command here +``` + + + diff --git a/melos.yaml b/melos.yaml new file mode 100644 index 000000000..2c94ec58c --- /dev/null +++ b/melos.yaml @@ -0,0 +1,42 @@ +name: flutter_deriv_packages +repository: https://github.com/regentmarkets/flutter-deriv-packages + +packages: + - packages/* + +command: + version: + # Generate commit links in package changelogs. + linkToCommits: true + workspaceChangelog: true + updateGitTagRefs: true + + bootstrap: + # It seems so that running "pub get" in parallel has some issues (like + # https://github.com/dart-lang/pub/issues/3404). Disabling this feature + # makes the CI much more stable. + runPubGetInParallel: false + +scripts: + analyze: + name: Flutter Analyze + description: Run flutter analyze for all packages. + ## Only fail if there are errors or warnings and not on infos. + run: | + if flutter analyze 2>&1 | grep -q -E 'error:|warning:'; + then exit 1 + else exit 0 + fi + exec: + concurrency: 1 + failFast: true + + test: + name: Flutter Test + description: Run flutter test for all packages. + run: flutter test --coverage + exec: + failFast: true + packageFilters: + dirExists: + - test diff --git a/packages/analytics/CHANGELOG.md b/packages/analytics/CHANGELOG.md index 0a228ceff..df6cadb3a 100644 --- a/packages/analytics/CHANGELOG.md +++ b/packages/analytics/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.0.1 + + - **REVERT**(analytics): versioning and CHANGELOG. ([81a3a0df](https://github.com/regentmarkets/flutter-deriv-packages/commit/81a3a0df27208bd200009415855c6cb944d016e3)) + - **REFACTOR**(analytics): [MOBC-546] Creating unified analytics package. ([#315](https://github.com/regentmarkets/flutter-deriv-packages/issues/315)). ([fd1d8ed3](https://github.com/regentmarkets/flutter-deriv-packages/commit/fd1d8ed345d4ecf91c5f6c1463c5196b40abcbf6)) + ## [1.0.0] - Migrated the package to null safety. diff --git a/packages/analytics/README.md b/packages/analytics/README.md index 1b38b96a5..a2d25a263 100644 --- a/packages/analytics/README.md +++ b/packages/analytics/README.md @@ -1,121 +1,127 @@ -# analytics -*** -This package is used for collecting and sending analytical information from the app to "Firebase" and "RudderStack". +# Deriv Analytics Library + +A Flutter analytics library that integrates multiple analytics services including Datadog, Firebase, and RudderStack. + +## Table of Contents + +1. [Installation](#installation) +2. [Quick Start](#quick-start) +3. [Configuration](#configuration) + - [Datadog](#datadog) + - [Firebase](#firebase) + - [RudderStack](#rudderstack) +4. [Logging Events](#logging-events) +5. [User Identification](#user-identification) +6. [Tracking Screens](#tracking-screens) + ## Installation -##### 1. Add to pubspec.yaml + +To install the package, add the following to your `pubspec.yaml`: + ```yaml -analytics: +dependencies: + analytics: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/analytics ref: ``` -Setup the Android and iOS sources as described at rudderstack.com and generate the write keys for Android and iOS sources. - -Set the WRITE_KEY in addition to enabling the plugin to track app lifecycle, screens, and whether to enable debug mode or not in Android and iOS as follows: - -### Android -```xml - - - - [...] - - - - - - - - - -``` -### iOS -```xml - - - - - [...] - com.deriv.rudderstack.WRITE_KEY - ADD-YOUR-KEY - com.deriv.rudderstack.TRACK_APPLICATION_LIFECYCLE_EVENTS - - com.deriv.rudderstack.RECORD_SCREEN_VIEWS - - com.deriv.rudderstack.DEBUG - - [...] - - -``` +Run `flutter packages get` to fetch the package. + +## Quick Start + +Initialize the analytics services you want to use in your `main.dart`: -## How to use -*** -##### 1. Enabling analytics. -```dart -Analytics().init( - isEnabled: true, // set value to false for disable 'Analytics' - ); -``` -##### 2. To track PageRoute transitions. -```dart -MaterialApp( - navigatorObservers: Analytics().observer == null - ? [] - : [Analytics().observer], - ); -``` -##### 3. Logging to 'RudderStack' in different scenarios. -###### when app is opened. -```dart -Analytics().logAppOpened(); -``` -###### when app is in background. ```dart -Analytics().logAppBackgrounded(); +final datadog = DerivDatadog(); +final firebase = DerivFirebaseAnalytics(); +final rudderstack = DerivRudderstack(); + +void main() { + + WidgetsFlutterBinding.ensureInitialized(); + + DerivDatadogConfiguration configuration = DerivDatadogConfiguration( + applicationId: 'DATADOG_APPLICATION_ID', + clientToken: 'DATADOG_CLIENT_TOKEN', + env: 'DATADOG_ENVIRONMENT', + serviceName: 'DATADOG_SERVICE_NAME', + trackingConsent: TrackingConsent.granted, + ); + + DerivDatadog().setup(configuration); + + DerivRudderstack().setup(RudderstackConfiguration( + dataPlaneUrl: 'RUDDERSTACK_DATA_PLANE_URL', + writeKey: 'RUDDERSTACK_WRITE_KEY', + debugEnabled: true, + )); + + DerivFirebaseAnalytics(FirebaseAnalytics.instanceFor(app: await Firebase.initializeApp())).setup( + FirebaseConfiguration( + isAnalyticsCollectionEnabled: true, + ), + ); + + runApp(MyApp()); +} ``` -###### when app is crashed. + +## Configuration + +### Datadog + ```dart -Analytics().logAppCrashed(); +await datadog.setup(DerivDatadogConfiguration( + clientToken: 'your_client_token', + applicationId: 'your_application_id', + env: 'production', + trackingConsent: TrackingConsent.granted, +)); ``` -##### 4. Sending information about current screen. +### Firebase + ```dart -Analytics().setCurrentScreen(screenName: ""); +await firebase.setup(FirebaseConfiguration( + isAnalyticsCollectionEnabled: true, +)); ``` -##### 4. Setting routes/screens which need to be ignored for analytics. + +### RudderStack + ```dart -Analytics().setIgnoredRoutes([ - 'IGNORED_SCREEN_NAME_1', - 'IGNORED_SCREEN_NAME_2', - '.....................', - 'IGNORED_SCREEN_NAME_N' - ]); +await rudderstack.setup(RudderstackConfiguration( + dataPlaneUrl: 'your_data_plane_url', + writeKey: 'your_write_key', +)); ``` -##### 4. Sending information during user login. + +## Logging Events + +You can log events as follows: + ```dart -Analytics().logLoginEvent(userId: "",); +datadog.logEvent('button_click', {'label': 'cta_button'}); +firebase.logEvent('button_click', {'label': 'cta_button'}); +rudderstack.track('button_click'); ``` -##### 7. Sending information during user logout. + +## User Identification + +To identify a user: + ```dart -Analytics().logLogoutEvent(); +datadog.setUserInfo(id: '123', email: 'email@example.com'); +firebase.setUserId(id: '123'); +rudderstack.identify(userId: '123'); ``` -##### 8. Sending information about important events to "Firebase". + +## Tracking Screens + ```dart -Analytics().logToFirebase( - name: "", - params: {'PARAM_1': 'VALUE_1', - 'PARAM_1': 'VALUE_1', - '.......': '.......', - 'PARAM_N': 'VALUE_N'}, - ); +datadog.screen(screenName: 'Home'); +firebase.setCurrentScreen(screenName: 'Home'); +rudderstack.screen(screenName: 'Home'); ``` diff --git a/packages/analytics/example/.env.example b/packages/analytics/example/.env.example new file mode 100644 index 000000000..ccbb3537c --- /dev/null +++ b/packages/analytics/example/.env.example @@ -0,0 +1,9 @@ +DATADOG_APPLICATION_ID = +DATADOG_CLIENT_TOKEN = +DATADOG_ENVIRONMENT = +DATADOG_SERVICE_NAME = + + + +RUDDERSTACK_DATA_PLANE_URL = +RUDDERSTACK_WRITE_KEY = diff --git a/packages/analytics/example/.gitignore b/packages/analytics/example/.gitignore new file mode 100644 index 000000000..a62ba7b84 --- /dev/null +++ b/packages/analytics/example/.gitignore @@ -0,0 +1,48 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +.env \ No newline at end of file diff --git a/packages/analytics/example/.metadata b/packages/analytics/example/.metadata new file mode 100644 index 000000000..3c3e4b52f --- /dev/null +++ b/packages/analytics/example/.metadata @@ -0,0 +1,10 @@ +# 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: 5464c5bac742001448fe4fc0597be939379f88ea + channel: stable + +project_type: app diff --git a/packages/analytics/example/README.md b/packages/analytics/example/README.md new file mode 100644 index 000000000..6b3bf8eb5 --- /dev/null +++ b/packages/analytics/example/README.md @@ -0,0 +1,9 @@ +# Analytics Example App + +## How to run + +1. Clone this repository +2. Run `cp .env.example .env` +3. Add your RudderStack and Datadog configurations to `.env` +4. Run `flutter pub get` +5. Run `flutter run` \ No newline at end of file diff --git a/packages/analytics/example/analysis_options.yaml b/packages/analytics/example/analysis_options.yaml new file mode 100644 index 000000000..61b6c4de1 --- /dev/null +++ b/packages/analytics/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/analytics/example/android/.gitignore b/packages/analytics/example/android/.gitignore new file mode 100644 index 000000000..6f568019d --- /dev/null +++ b/packages/analytics/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/analytics/example/android/app/build.gradle b/packages/analytics/example/android/app/build.gradle new file mode 100644 index 000000000..806b0464e --- /dev/null +++ b/packages/analytics/example/android/app/build.gradle @@ -0,0 +1,68 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 21 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.example" + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32:$kotlin_version" +} diff --git a/packages/analytics/example/android/app/src/debug/AndroidManifest.xml b/packages/analytics/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..c208884f3 --- /dev/null +++ b/packages/analytics/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/analytics/example/android/app/src/main/AndroidManifest.xml b/packages/analytics/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..3f41384db --- /dev/null +++ b/packages/analytics/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/packages/analytics/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/analytics/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 000000000..e793a000d --- /dev/null +++ b/packages/analytics/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/analytics/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/analytics/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/analytics/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/analytics/example/android/app/src/main/res/drawable/launch_background.xml b/packages/analytics/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/analytics/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/analytics/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/analytics/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/analytics/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/analytics/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/packages/analytics/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/analytics/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/packages/analytics/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/analytics/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/packages/analytics/example/android/app/src/main/res/values-night/styles.xml b/packages/analytics/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..3db14bb53 --- /dev/null +++ b/packages/analytics/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/analytics/example/android/app/src/main/res/values/styles.xml b/packages/analytics/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..d460d1e92 --- /dev/null +++ b/packages/analytics/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/analytics/example/android/app/src/profile/AndroidManifest.xml b/packages/analytics/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..c208884f3 --- /dev/null +++ b/packages/analytics/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/analytics/example/android/build.gradle b/packages/analytics/example/android/build.gradle new file mode 100644 index 000000000..aa282b69a --- /dev/null +++ b/packages/analytics/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/analytics/example/android/gradle.properties b/packages/analytics/example/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/packages/analytics/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/analytics/example/android/settings.gradle b/packages/analytics/example/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/packages/analytics/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/analytics/example/ios/.gitignore b/packages/analytics/example/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/packages/analytics/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/analytics/example/ios/Flutter/AppFrameworkInfo.plist b/packages/analytics/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..9625e105d --- /dev/null +++ b/packages/analytics/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/packages/analytics/example/ios/Flutter/Debug.xcconfig b/packages/analytics/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/packages/analytics/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/analytics/example/ios/Flutter/Release.xcconfig b/packages/analytics/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/packages/analytics/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/analytics/example/ios/Podfile b/packages/analytics/example/ios/Podfile new file mode 100644 index 000000000..68df00b22 --- /dev/null +++ b/packages/analytics/example/ios/Podfile @@ -0,0 +1,50 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +# post_install do |installer| +# installer.pods_project.targets.each do |target| +# flutter_additional_ios_build_settings(target) +# end +# end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' + end + end + end diff --git a/packages/analytics/example/ios/Podfile.lock b/packages/analytics/example/ios/Podfile.lock new file mode 100644 index 000000000..4be589ed5 --- /dev/null +++ b/packages/analytics/example/ios/Podfile.lock @@ -0,0 +1,178 @@ +PODS: + - datadog_flutter_plugin (0.0.1): + - DatadogSDK (= 1.23.0) + - DatadogSDKCrashReporting (= 1.23.0) + - DictionaryCoder (= 1.0.8) + - Flutter + - DatadogSDK (1.23.0) + - DatadogSDKCrashReporting (1.23.0): + - DatadogSDK (= 1.23.0) + - PLCrashReporter (~> 1.11.0) + - DictionaryCoder (1.0.8) + - Firebase/Analytics (10.15.0): + - Firebase/Core + - Firebase/Core (10.15.0): + - Firebase/CoreOnly + - FirebaseAnalytics (~> 10.15.0) + - Firebase/CoreOnly (10.15.0): + - FirebaseCore (= 10.15.0) + - firebase_analytics (10.6.1): + - Firebase/Analytics (= 10.15.0) + - firebase_core + - Flutter + - firebase_core (2.19.0): + - Firebase/CoreOnly (= 10.15.0) + - Flutter + - FirebaseAnalytics (10.15.0): + - FirebaseAnalytics/AdIdSupport (= 10.15.0) + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseAnalytics/AdIdSupport (10.15.0): + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleAppMeasurement (= 10.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseCore (10.15.0): + - FirebaseCoreInternal (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/Logger (~> 7.8) + - FirebaseCoreInternal (10.16.0): + - "GoogleUtilities/NSData+zlib (~> 7.8)" + - FirebaseInstallations (10.16.0): + - FirebaseCore (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/UserDefaults (~> 7.8) + - PromisesObjC (~> 2.1) + - Flutter (1.0.0) + - GoogleAppMeasurement (10.15.0): + - GoogleAppMeasurement/AdIdSupport (= 10.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30910.0, >= 2.30908.0) + - GoogleAppMeasurement/AdIdSupport (10.15.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30910.0, >= 2.30908.0) + - GoogleAppMeasurement/WithoutAdIdSupport (10.15.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30910.0, >= 2.30908.0) + - GoogleUtilities/AppDelegateSwizzler (7.11.5): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (7.11.5): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.11.5): + - GoogleUtilities/Environment + - GoogleUtilities/MethodSwizzler (7.11.5): + - GoogleUtilities/Logger + - GoogleUtilities/Network (7.11.5): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (7.11.5)" + - GoogleUtilities/Reachability (7.11.5): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (7.11.5): + - GoogleUtilities/Logger + - MetricsReporter (1.1.1): + - RSCrashReporter (= 1.0.0) + - RudderKit (= 1.4.0) + - nanopb (2.30909.0): + - nanopb/decode (= 2.30909.0) + - nanopb/encode (= 2.30909.0) + - nanopb/decode (2.30909.0) + - nanopb/encode (2.30909.0) + - PLCrashReporter (1.11.1) + - PromisesObjC (2.3.1) + - RSCrashReporter (1.0.0) + - Rudder (1.23.0): + - MetricsReporter (= 1.1.1) + - rudder_plugin_ios (0.0.1): + - Flutter + - Rudder (< 2.0.0, >= 1.23.0) + - RudderKit (1.4.0) + +DEPENDENCIES: + - datadog_flutter_plugin (from `.symlinks/plugins/datadog_flutter_plugin/ios`) + - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - Flutter (from `Flutter`) + - rudder_plugin_ios (from `.symlinks/plugins/rudder_plugin_ios/ios`) + +SPEC REPOS: + trunk: + - DatadogSDK + - DatadogSDKCrashReporting + - DictionaryCoder + - Firebase + - FirebaseAnalytics + - FirebaseCore + - FirebaseCoreInternal + - FirebaseInstallations + - GoogleAppMeasurement + - GoogleUtilities + - MetricsReporter + - nanopb + - PLCrashReporter + - PromisesObjC + - RSCrashReporter + - Rudder + - RudderKit + +EXTERNAL SOURCES: + datadog_flutter_plugin: + :path: ".symlinks/plugins/datadog_flutter_plugin/ios" + firebase_analytics: + :path: ".symlinks/plugins/firebase_analytics/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + Flutter: + :path: Flutter + rudder_plugin_ios: + :path: ".symlinks/plugins/rudder_plugin_ios/ios" + +SPEC CHECKSUMS: + datadog_flutter_plugin: 8f47e24d1953bd48658d44bb557e173534f26ac2 + DatadogSDK: c2519eea53cc46c17d28a90b9784c3b265afdb52 + DatadogSDKCrashReporting: c3d443bad91788a9c4f781e079fa0a43564bedd7 + DictionaryCoder: 5f84fff69f54cb806071538430bdafe04a89d658 + Firebase: 66043bd4579e5b73811f96829c694c7af8d67435 + firebase_analytics: 8b63b894ac3be5ced6a9a4614174754b03377c2f + firebase_core: fd674fcc642742ef7289acea60bd21a1a021bd98 + FirebaseAnalytics: 47cef43728f81a839cf1306576bdd77ffa2eac7e + FirebaseCore: 2cec518b43635f96afe7ac3a9c513e47558abd2e + FirebaseCoreInternal: 26233f705cc4531236818a07ac84d20c333e505a + FirebaseInstallations: b822f91a61f7d1ba763e5ccc9d4f2e6f2ed3b3ee + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + GoogleAppMeasurement: 722db6550d1e6d552b08398b69a975ac61039338 + GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084 + MetricsReporter: 759631361ffd2b8f0d375b1225c8a631311f6da2 + nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 + PLCrashReporter: 5d2d3967afe0efad61b3048d617e2199a5d1b787 + PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 + RSCrashReporter: e9ccaebd996263f8325a6cbef44ff74aa5f58e30 + Rudder: 125d9dc03e178b35b0f74487aa694afe1a8e6f4f + rudder_plugin_ios: 940ac533f6b3882d7d4389d084aeb2a844c8f4c4 + RudderKit: f272f9872183946452ac94cd7bb2244a71e6ca8f + +PODFILE CHECKSUM: 9eb4a36c9b6b15b2354428bb0f3d2873ffa3b2e6 + +COCOAPODS: 1.13.0 diff --git a/packages/analytics/example/ios/Runner.xcodeproj/project.pbxproj b/packages/analytics/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..86dcea23b --- /dev/null +++ b/packages/analytics/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,552 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 083EBA9E56DE58145841DF61 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52E312214333A7F75C8F00F6 /* Pods_Runner.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 482409292D5D6DE86C778A03 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 52E312214333A7F75C8F00F6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 83DB042B106A298E00E72DAC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9995D6A89D80469EC59877B9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 083EBA9E56DE58145841DF61 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + C6A8115C1B6376D40F79AA1D /* Pods */, + ADB2A804837615BFCC32E1C6 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + ADB2A804837615BFCC32E1C6 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 52E312214333A7F75C8F00F6 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + C6A8115C1B6376D40F79AA1D /* Pods */ = { + isa = PBXGroup; + children = ( + 83DB042B106A298E00E72DAC /* Pods-Runner.debug.xcconfig */, + 482409292D5D6DE86C778A03 /* Pods-Runner.release.xcconfig */, + 9995D6A89D80469EC59877B9 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + F034C2F5ACDA6DC94A5B86B5 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 5538329A9D37E08C0AD65335 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 5538329A9D37E08C0AD65335 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + F034C2F5ACDA6DC94A5B86B5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/analytics/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/analytics/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..a6b826db2 --- /dev/null +++ b/packages/analytics/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/analytics/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/analytics/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/packages/analytics/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/analytics/example/ios/Runner/AppDelegate.swift b/packages/analytics/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/packages/analytics/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 zcmeHN2~<R zh`|8`A_PQ1nSu(UMFx?8j8PC!!VDphaL#`F42fd#7Vlc`zIE4n%Y~eiz4y1j|NDpi z?<@|pSJ-HM`qifhf@m%MamgwK83`XpBA<+azdF#2QsT{X@z0A9Bq>~TVErigKH1~P zRX-!h-f0NJ4Mh++{D}J+K>~~rq}d%o%+4dogzXp7RxX4C>Km5XEI|PAFDmo;DFm6G zzjVoB`@qW98Yl0Kvc-9w09^PrsobmG*Eju^=3f?0o-t$U)TL1B3;sZ^!++3&bGZ!o-*6w?;oOhf z=A+Qb$scV5!RbG+&2S}BQ6YH!FKb0``VVX~T$dzzeSZ$&9=X$3)_7Z{SspSYJ!lGE z7yig_41zpQ)%5dr4ff0rh$@ky3-JLRk&DK)NEIHecf9c*?Z1bUB4%pZjQ7hD!A0r-@NF(^WKdr(LXj|=UE7?gBYGgGQV zidf2`ZT@pzXf7}!NH4q(0IMcxsUGDih(0{kRSez&z?CFA0RVXsVFw3^u=^KMtt95q z43q$b*6#uQDLoiCAF_{RFc{!H^moH_cmll#Fc^KXi{9GDl{>%+3qyfOE5;Zq|6#Hb zp^#1G+z^AXfRKaa9HK;%b3Ux~U@q?xg<2DXP%6k!3E)PA<#4$ui8eDy5|9hA5&{?v z(-;*1%(1~-NTQ`Is1_MGdQ{+i*ccd96ab$R$T3=% zw_KuNF@vI!A>>Y_2pl9L{9h1-C6H8<)J4gKI6{WzGBi<@u3P6hNsXG=bRq5c+z;Gc3VUCe;LIIFDmQAGy+=mRyF++u=drBWV8-^>0yE9N&*05XHZpPlE zxu@?8(ZNy7rm?|<+UNe0Vs6&o?l`Pt>P&WaL~M&#Eh%`rg@Mbb)J&@DA-wheQ>hRV z<(XhigZAT z>=M;URcdCaiO3d^?H<^EiEMDV+7HsTiOhoaMX%P65E<(5xMPJKxf!0u>U~uVqnPN7T!X!o@_gs3Ct1 zlZ_$5QXP4{Aj645wG_SNT&6m|O6~Tsl$q?nK*)(`{J4b=(yb^nOATtF1_aS978$x3 zx>Q@s4i3~IT*+l{@dx~Hst21fR*+5}S1@cf>&8*uLw-0^zK(+OpW?cS-YG1QBZ5q! zgTAgivzoF#`cSz&HL>Ti!!v#?36I1*l^mkrx7Y|K6L#n!-~5=d3;K<;Zqi|gpNUn_ z_^GaQDEQ*jfzh;`j&KXb66fWEk1K7vxQIMQ_#Wu_%3 z4Oeb7FJ`8I>Px;^S?)}2+4D_83gHEq>8qSQY0PVP?o)zAv3K~;R$fnwTmI-=ZLK`= zTm+0h*e+Yfr(IlH3i7gUclNH^!MU>id$Jw>O?2i0Cila#v|twub21@e{S2v}8Z13( zNDrTXZVgris|qYm<0NU(tAPouG!QF4ZNpZPkX~{tVf8xY690JqY1NVdiTtW+NqyRP zZ&;T0ikb8V{wxmFhlLTQ&?OP7 z;(z*<+?J2~z*6asSe7h`$8~Se(@t(#%?BGLVs$p``;CyvcT?7Y!{tIPva$LxCQ&4W z6v#F*);|RXvI%qnoOY&i4S*EL&h%hP3O zLsrFZhv&Hu5tF$Lx!8(hs&?!Kx5&L(fdu}UI5d*wn~A`nPUhG&Rv z2#ixiJdhSF-K2tpVL=)5UkXRuPAFrEW}7mW=uAmtVQ&pGE-&az6@#-(Te^n*lrH^m@X-ftVcwO_#7{WI)5v(?>uC9GG{lcGXYJ~Q8q zbMFl7;t+kV;|;KkBW2!P_o%Czhw&Q(nXlxK9ak&6r5t_KH8#1Mr-*0}2h8R9XNkr zto5-b7P_auqTJb(TJlmJ9xreA=6d=d)CVbYP-r4$hDn5|TIhB>SReMfh&OVLkMk-T zYf%$taLF0OqYF?V{+6Xkn>iX@TuqQ?&cN6UjC9YF&%q{Ut3zv{U2)~$>-3;Dp)*(? zg*$mu8^i=-e#acaj*T$pNowo{xiGEk$%DusaQiS!KjJH96XZ-hXv+jk%ard#fu=@Q z$AM)YWvE^{%tDfK%nD49=PI|wYu}lYVbB#a7wtN^Nml@CE@{Gv7+jo{_V?I*jkdLD zJE|jfdrmVbkfS>rN*+`#l%ZUi5_bMS<>=MBDNlpiSb_tAF|Zy`K7kcp@|d?yaTmB^ zo?(vg;B$vxS|SszusORgDg-*Uitzdi{dUV+glA~R8V(?`3GZIl^egW{a919!j#>f` znL1o_^-b`}xnU0+~KIFLQ)$Q6#ym%)(GYC`^XM*{g zv3AM5$+TtDRs%`2TyR^$(hqE7Y1b&`Jd6dS6B#hDVbJlUXcG3y*439D8MrK!2D~6gn>UD4Imctb z+IvAt0iaW73Iq$K?4}H`7wq6YkTMm`tcktXgK0lKPmh=>h+l}Y+pDtvHnG>uqBA)l zAH6BV4F}v$(o$8Gfo*PB>IuaY1*^*`OTx4|hM8jZ?B6HY;F6p4{`OcZZ(us-RVwDx zUzJrCQlp@mz1ZFiSZ*$yX3c_#h9J;yBE$2g%xjmGF4ca z&yL`nGVs!Zxsh^j6i%$a*I3ZD2SoNT`{D%mU=LKaEwbN(_J5%i-6Va?@*>=3(dQy` zOv%$_9lcy9+(t>qohkuU4r_P=R^6ME+wFu&LA9tw9RA?azGhjrVJKy&8=*qZT5Dr8g--d+S8zAyJ$1HlW3Olryt`yE zFIph~Z6oF&o64rw{>lgZISC6p^CBer9C5G6yq%?8tC+)7*d+ib^?fU!JRFxynRLEZ zj;?PwtS}Ao#9whV@KEmwQgM0TVP{hs>dg(1*DiMUOKHdQGIqa0`yZnHk9mtbPfoLx zo;^V6pKUJ!5#n`w2D&381#5#_t}AlTGEgDz$^;u;-vxDN?^#5!zN9ngytY@oTv!nc zp1Xn8uR$1Z;7vY`-<*?DfPHB;x|GUi_fI9@I9SVRv1)qETbNU_8{5U|(>Du84qP#7 z*l9Y$SgA&wGbj>R1YeT9vYjZuC@|{rajTL0f%N@>3$DFU=`lSPl=Iv;EjuGjBa$Gw zHD-;%YOE@<-!7-Mn`0WuO3oWuL6tB2cpPw~Nvuj|KM@))ixuDK`9;jGMe2d)7gHin zS<>k@!x;!TJEc#HdL#RF(`|4W+H88d4V%zlh(7#{q2d0OQX9*FW^`^_<3r$kabWAB z$9BONo5}*(%kx zOXi-yM_cmB3>inPpI~)duvZykJ@^^aWzQ=eQ&STUa}2uT@lV&WoRzkUoE`rR0)`=l zFT%f|LA9fCw>`enm$p7W^E@U7RNBtsh{_-7vVz3DtB*y#*~(L9+x9*wn8VjWw|Q~q zKFsj1Yl>;}%MG3=PY`$g$_mnyhuV&~O~u~)968$0b2!Jkd;2MtAP#ZDYw9hmK_+M$ zb3pxyYC&|CuAbtiG8HZjj?MZJBFbt`ryf+c1dXFuC z0*ZQhBzNBd*}s6K_G}(|Z_9NDV162#y%WSNe|FTDDhx)K!c(mMJh@h87@8(^YdK$&d*^WQe8Z53 z(|@MRJ$Lk-&ii74MPIs80WsOFZ(NX23oR-?As+*aq6b?~62@fSVmM-_*cb1RzZ)`5$agEiL`-E9s7{GM2?(KNPgK1(+c*|-FKoy}X(D_b#etO|YR z(BGZ)0Ntfv-7R4GHoXp?l5g#*={S1{u-QzxCGng*oWr~@X-5f~RA14b8~B+pLKvr4 zfgL|7I>jlak9>D4=(i(cqYf7#318!OSR=^`xxvI!bBlS??`xxWeg?+|>MxaIdH1U~#1tHu zB{QMR?EGRmQ_l4p6YXJ{o(hh-7Tdm>TAX380TZZZyVkqHNzjUn*_|cb?T? zt;d2s-?B#Mc>T-gvBmQZx(y_cfkXZO~{N zT6rP7SD6g~n9QJ)8F*8uHxTLCAZ{l1Y&?6v)BOJZ)=R-pY=Y=&1}jE7fQ>USS}xP#exo57uND0i*rEk@$;nLvRB@u~s^dwRf?G?_enN@$t* zbL%JO=rV(3Ju8#GqUpeE3l_Wu1lN9Y{D4uaUe`g>zlj$1ER$6S6@{m1!~V|bYkhZA z%CvrDRTkHuajMU8;&RZ&itnC~iYLW4DVkP<$}>#&(`UO>!n)Po;Mt(SY8Yb`AS9lt znbX^i?Oe9r_o=?})IHKHoQGKXsps_SE{hwrg?6dMI|^+$CeC&z@*LuF+P`7LfZ*yr+KN8B4{Nzv<`A(wyR@!|gw{zB6Ha ziwPAYh)oJ(nlqSknu(8g9N&1hu0$vFK$W#mp%>X~AU1ay+EKWcFdif{% z#4!4aoVVJ;ULmkQf!ke2}3hqxLK>eq|-d7Ly7-J9zMpT`?dxo6HdfJA|t)?qPEVBDv z{y_b?4^|YA4%WW0VZd8C(ZgQzRI5(I^)=Ub`Y#MHc@nv0w-DaJAqsbEHDWG8Ia6ju zo-iyr*sq((gEwCC&^TYBWt4_@|81?=B-?#P6NMff(*^re zYqvDuO`K@`mjm_Jd;mW_tP`3$cS?R$jR1ZN09$YO%_iBqh5ftzSpMQQtxKFU=FYmP zeY^jph+g<4>YO;U^O>-NFLn~-RqlHvnZl2yd2A{Yc1G@Ga$d+Q&(f^tnPf+Z7serIU};17+2DU_f4Z z@GaPFut27d?!YiD+QP@)T=77cR9~MK@bd~pY%X(h%L={{OIb8IQmf-!xmZkm8A0Ga zQSWONI17_ru5wpHg3jI@i9D+_Y|pCqVuHJNdHUauTD=R$JcD2K_liQisqG$(sm=k9;L* z!L?*4B~ql7uioSX$zWJ?;q-SWXRFhz2Jt4%fOHA=Bwf|RzhwqdXGr78y$J)LR7&3T zE1WWz*>GPWKZ0%|@%6=fyx)5rzUpI;bCj>3RKzNG_1w$fIFCZ&UR0(7S?g}`&Pg$M zf`SLsz8wK82Vyj7;RyKmY{a8G{2BHG%w!^T|Njr!h9TO2LaP^_f22Q1=l$QiU84ao zHe_#{S6;qrC6w~7{y(hs-?-j?lbOfgH^E=XcSgnwW*eEz{_Z<_Px$?ny*JR5%f>l)FnDQ543{x%ZCiu33$Wg!pQFfT_}?5Q|_VSlIbLC`dpoMXL}9 zHfd9&47Mo(7D231gb+kjFxZHS4-m~7WurTH&doVX2KI5sU4v(sJ1@T9eCIKPjsqSr z)C01LsCxk=72-vXmX}CQD#BD;Cthymh&~=f$Q8nn0J<}ZrusBy4PvRNE}+1ceuj8u z0mW5k8fmgeLnTbWHGwfKA3@PdZxhn|PypR&^p?weGftrtCbjF#+zk_5BJh7;0`#Wr zgDpM_;Ax{jO##IrT`Oz;MvfwGfV$zD#c2xckpcXC6oou4ML~ezCc2EtnsQTB4tWNg z?4bkf;hG7IMfhgNI(FV5Gs4|*GyMTIY0$B=_*mso9Ityq$m^S>15>-?0(zQ<8Qy<_TjHE33(?_M8oaM zyc;NxzRVK@DL6RJnX%U^xW0Gpg(lXp(!uK1v0YgHjs^ZXSQ|m#lV7ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 literal 0 HcmV?d00001 diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 zcmds$`#;kQ7{|XelZftyR5~xW7?MLxS4^|Hw3&P7^y)@A9Fj{Xm1~_CIV^XZ%SLBn zA;!r`GqGHg=7>xrB{?psZQs88ZaedDoagm^KF{a*>G|dJWRSe^I$DNW008I^+;Kjt z>9p3GNR^I;v>5_`+91i(*G;u5|L+Bu6M=(afLjtkya#yZ175|z$pU~>2#^Z_pCZ7o z1c6UNcv2B3?; zX%qdxCXQpdKRz=#b*q0P%b&o)5ZrNZt7$fiETSK_VaY=mb4GK`#~0K#~9^ zcY!`#Af+4h?UMR-gMKOmpuYeN5P*RKF!(tb`)oe0j2BH1l?=>y#S5pMqkx6i{*=V9JF%>N8`ewGhRE(|WohnD59R^$_36{4>S zDFlPC5|k?;SPsDo87!B{6*7eqmMdU|QZ84>6)Kd9wNfh90=y=TFQay-0__>=<4pk& zYDjgIhL-jQ9o>z32K)BgAH+HxamL{ZL~ozu)Qqe@a`FpH=oQRA8=L-m-1dam(Ix2V z?du;LdMO+ooBelr^_y4{|44tmgH^2hSzPFd;U^!1p>6d|o)(-01z{i&Kj@)z-yfWQ)V#3Uo!_U}q3u`(fOs`_f^ueFii1xBNUB z6MecwJN$CqV&vhc+)b(p4NzGGEgwWNs z@*lUV6LaduZH)4_g!cE<2G6#+hJrWd5(|p1Z;YJ7ifVHv+n49btR}dq?HHDjl{m$T z!jLZcGkb&XS2OG~u%&R$(X+Z`CWec%QKt>NGYvd5g20)PU(dOn^7%@6kQb}C(%=vr z{?RP(z~C9DPnL{q^@pVw@|Vx~@3v!9dCaBtbh2EdtoNHm4kGxp>i#ct)7p|$QJs+U z-a3qtcPvhihub?wnJqEt>zC@)2suY?%-96cYCm$Q8R%-8$PZYsx3~QOLMDf(piXMm zB=<63yQk1AdOz#-qsEDX>>c)EES%$owHKue;?B3)8aRd}m~_)>SL3h2(9X;|+2#7X z+#2)NpD%qJvCQ0a-uzZLmz*ms+l*N}w)3LRQ*6>|Ub-fyptY(keUxw+)jfwF5K{L9 z|Cl_w=`!l_o><384d&?)$6Nh(GAm=4p_;{qVn#hI8lqewW7~wUlyBM-4Z|)cZr?Rh z=xZ&Ol>4(CU85ea(CZ^aO@2N18K>ftl8>2MqetAR53_JA>Fal`^)1Y--Am~UDa4th zKfCYpcXky$XSFDWBMIl(q=Mxj$iMBX=|j9P)^fDmF(5(5$|?Cx}DKEJa&XZP%OyE`*GvvYQ4PV&!g2|L^Q z?YG}tx;sY@GzMmsY`7r$P+F_YLz)(e}% zyakqFB<6|x9R#TdoP{R$>o7y(-`$$p0NxJ6?2B8tH)4^yF(WhqGZlM3=9Ibs$%U1w zWzcss*_c0=v_+^bfb`kBFsI`d;ElwiU%frgRB%qBjn@!0U2zZehBn|{%uNIKBA7n= zzE`nnwTP85{g;8AkYxA68>#muXa!G>xH22D1I*SiD~7C?7Za+9y7j1SHiuSkKK*^O zsZ==KO(Ua#?YUpXl{ViynyT#Hzk=}5X$e04O@fsMQjb}EMuPWFO0e&8(2N(29$@Vd zn1h8Yd>6z(*p^E{c(L0Lg=wVdupg!z@WG;E0k|4a%s7Up5C0c)55XVK*|x9RQeZ1J@1v9MX;>n34(i>=YE@Iur`0Vah(inE3VUFZNqf~tSz{1fz3Fsn_x4F>o(Yo;kpqvBe-sbwH(*Y zu$JOl0b83zu$JMvy<#oH^Wl>aWL*?aDwnS0iEAwC?DK@aT)GHRLhnz2WCvf3Ba;o=aY7 z2{Asu5MEjGOY4O#Ggz@@J;q*0`kd2n8I3BeNuMmYZf{}pg=jTdTCrIIYuW~luKecn z+E-pHY%ohj@uS0%^ z&(OxwPFPD$+#~`H?fMvi9geVLci(`K?Kj|w{rZ9JgthFHV+=6vMbK~0)Ea<&WY-NC zy-PnZft_k2tfeQ*SuC=nUj4H%SQ&Y$gbH4#2sT0cU0SdFs=*W*4hKGpuR1{)mV;Qf5pw4? zfiQgy0w3fC*w&Bj#{&=7033qFR*<*61B4f9K%CQvxEn&bsWJ{&winp;FP!KBj=(P6 z4Z_n4L7cS;ao2)ax?Tm|I1pH|uLpDSRVghkA_UtFFuZ0b2#>!8;>-_0ELjQSD-DRd z4im;599VHDZYtnWZGAB25W-e(2VrzEh|etsv2YoP#VbIZ{aFkwPrzJ#JvCvA*mXS& z`}Q^v9(W4GiSs}#s7BaN!WA2bniM$0J(#;MR>uIJ^uvgD3GS^%*ikdW6-!VFUU?JV zZc2)4cMsX@j z5HQ^e3BUzOdm}yC-xA%SY``k$rbfk z;CHqifhU*jfGM@DkYCecD9vl*qr58l6x<8URB=&%{!Cu3RO*MrKZ4VO}V6R0a zZw3Eg^0iKWM1dcTYZ0>N899=r6?+adUiBKPciJw}L$=1f4cs^bio&cr9baLF>6#BM z(F}EXe-`F=f_@`A7+Q&|QaZ??Txp_dB#lg!NH=t3$G8&06MFhwR=Iu*Im0s_b2B@| znW>X}sy~m#EW)&6E&!*0%}8UAS)wjt+A(io#wGI@Z2S+Ms1Cxl%YVE800007ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 literal 0 HcmV?d00001 diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 zcmVjJBgitF5mAp-i>4+KS_oR{|13AP->1TD4=w)g|)JHOx|a2Wk1Va z!k)vP$UcQ#mdj%wNQoaJ!w>jv_6&JPyutpQps?s5dmDQ>`%?Bvj>o<%kYG!YW6H-z zu`g$@mp`;qDR!51QaS}|ZToSuAGcJ7$2HF0z`ln4t!#Yg46>;vGG9N9{V@9z#}6v* zfP?}r6b{*-C*)(S>NECI_E~{QYzN5SXRmVnP<=gzP+_Sp(Aza_hKlZ{C1D&l*(7IKXxQC1Z9#6wx}YrGcn~g%;icdw>T0Rf^w0{ z$_wn1J+C0@!jCV<%Go5LA45e{5gY9PvZp8uM$=1}XDI+9m7!A95L>q>>oe0$nC->i zeexUIvq%Uk<-$>DiDb?!In)lAmtuMWxvWlk`2>4lNuhSsjAf2*2tjT`y;@d}($o)S zn(+W&hJ1p0xy@oxP%AM15->wPLp{H!k)BdBD$toBpJh+crWdsNV)qsHaqLg2_s|Ih z`8E9z{E3sA!}5aKu?T!#enD(wLw?IT?k-yWVHZ8Akz4k5(TZJN^zZgm&zM28sfTD2BYJ|Fde3Xzh;;S` z=GXTnY4Xc)8nYoz6&vF;P7{xRF-{|2Xs5>a5)@BrnQ}I(_x7Cgpx#5&Td^4Q9_FnQ zX5so*;#8-J8#c$OlA&JyPp$LKUhC~-e~Ij!L%uSMu!-VZG7Hx-L{m2DVR2i=GR(_% zCVD!4N`I)&Q5S`?P&fQZ=4#Dgt_v2-DzkT}K(9gF0L(owe-Id$Rc2qZVLqI_M_DyO z9@LC#U28_LU{;wGZ&))}0R2P4MhajKCd^K#D+JJ&JIXZ_p#@+7J9A&P<0kdRujtQ_ zOy>3=C$kgi6$0pW06KaLz!21oOryKM3ZUOWqppndxfH}QpgjEJ`j7Tzn5bk6K&@RA?vl##y z$?V~1E(!wB5rH`>3nc&@)|#<1dN2cMzzm=PGhQ|Yppne(C-Vlt450IXc`J4R0W@I7 zd1e5uW6juvO%ni(WX7BsKx3MLngO7rHO;^R5I~0^nE^9^E_eYLgiR9&KnJ)pBbfno zSVnW$0R+&6jOOsZ82}nJ126+c|%svPo;TeUku<2G7%?$oft zyaO;tVo}(W)VsTUhq^XmFi#2z%-W9a{7mXn{uzivYQ_d6b7VJG{77naW(vHt-uhnY zVN#d!JTqVh(7r-lhtXVU6o})aZbDt_;&wJVGl2FKYFBFpU-#9U)z#(A%=IVnqytR$SY-sO( z($oNE09{D^@OuYPz&w~?9>Fl5`g9u&ecFGhqX=^#fmR=we0CJw+5xna*@oHnkahk+ z9aWeE3v|An+O5%?4fA&$Fgu~H_YmqR!yIU!bFCk4!#pAj%(lI(A5n)n@Id#M)O9Yx zJU9oKy{sRAIV3=5>(s8n{8ryJ!;ho}%pn6hZKTKbqk=&m=f*UnK$zW3YQP*)pw$O* zIfLA^!-bmBl6%d_n$#tP8Zd_(XdA*z*WH|E_yILwjtI~;jK#v-6jMl^?<%Y%`gvpwv&cFb$||^v4D&V=aNy?NGo620jL3VZnA%s zH~I|qPzB~e(;p;b^gJr7Ure#7?8%F0m4vzzPy^^(q4q1OdthF}Fi*RmVZN1OwTsAP zn9CZP`FazX3^kG(KodIZ=Kty8DLTy--UKfa1$6XugS zk%6v$Kmxt6U!YMx0JQ)0qX*{CXwZZk$vEROidEc7=J-1;peNat!vS<3P-FT5po>iE z!l3R+<`#x|+_hw!HjQGV=8!q|76y8L7N8gP3$%0kfush|u0uU^?dKBaeRSBUpOZ0c z62;D&Mdn2}N}xHRFTRI?zRv=>=AjHgH}`2k4WK=#AHB)UFrR-J87GgX*x5fL^W2#d z=(%K8-oZfMO=i{aWRDg=FX}UubM4eotRDcn;OR#{3q=*?3mE3_oJ-~prjhxh%PgQT zyn)Qozaq0@o&|LEgS{Ind4Swsr;b`u185hZPOBLL<`d2%^Yp1?oL)=jnLi;Zo0ZDliTtQ^b5SmfIMe{T==zZkbvn$KTQGlbG8w}s@M3TZnde;1Am46P3juKb zl9GU&3F=q`>j!`?SyH#r@O59%@aMX^rx}Nxe<>NqpUp5=lX1ojGDIR*-D^SDuvCKF z?3$xG(gVUsBERef_YjPFl^rU9EtD{pt z0CXwpN7BN3!8>hajGaTVk-wl=9rxmfWtIhC{mheHgStLi^+Nz12a?4r(fz)?3A%at zMlvQmL<2-R)-@G1wJ0^zQK%mR=r4d{Y3fHp){nWXUL#|CqXl(+v+qDh>FkF9`eWrW zfr^D%LNfOcTNvtx0JXR35J0~Jpi2#P3Q&80w+nqNfc}&G0A~*)lGHKv=^FE+b(37|)zL;KLF>oiGfb(?&1 zV3XRu!Sw>@quKiab%g6jun#oZ%!>V#A%+lNc?q>6+VvyAn=kf_6z^(TZUa4Eelh{{ zqFX-#dY(EV@7l$NE&kv9u9BR8&Ojd#ZGJ6l8_BW}^r?DIS_rU2(XaGOK z225E@kH5Opf+CgD^{y29jD4gHbGf{1MD6ggQ&%>UG4WyPh5q_tb`{@_34B?xfSO*| zZv8!)q;^o-bz`MuxXk*G^}(6)ACb@=Lfs`Hxoh>`Y0NE8QRQ!*p|SH@{r8=%RKd4p z+#Ty^-0kb=-H-O`nAA3_6>2z(D=~Tbs(n8LHxD0`R0_ATFqp-SdY3(bZ3;VUM?J=O zKCNsxsgt@|&nKMC=*+ZqmLHhX1KHbAJs{nGVMs6~TiF%Q)P@>!koa$%oS zjXa=!5>P`vC-a}ln!uH1ooeI&v?=?v7?1n~P(wZ~0>xWxd_Aw;+}9#eULM7M8&E?Y zC-ZLhi3RoM92SXUb-5i-Lmt5_rfjE{6y^+24`y$1lywLyHO!)Boa7438K4#iLe?rh z2O~YGSgFUBH?og*6=r9rme=peP~ah`(8Zt7V)j5!V0KPFf_mebo3z95U8(up$-+EA^9dTRLq>Yl)YMBuch9%=e5B`Vnb>o zt03=kq;k2TgGe4|lGne&zJa~h(UGutjP_zr?a7~#b)@15XNA>Dj(m=gg2Q5V4-$)D|Q9}R#002ovPDHLkV1o7DH3k3x literal 0 HcmV?d00001 diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 zcmV-m2cP(fP)x~L`~4d)Rspd&<9kFh{hn*KP1LP0~$;u(LfAu zp%fx&qLBcRHx$G|3q(bv@+b;o0*D|jwD-Q9uQR(l*ST}s+uPgQ-MeFwZ#GS?b332? z&Tk$&_miXn3IGq)AmQ)3sisq{raD4(k*bHvpCe-TdWq^NRTEVM)i9xbgQ&ccnUVx* zEY%vS%gDcSg=!tuIK8$Th2_((_h^+7;R|G{n06&O2#6%LK`a}n?h_fL18btz<@lFG za}xS}u?#DBMB> zw^b($1Z)`9G?eP95EKi&$eOy@K%h;ryrR3la%;>|o*>CgB(s>dDcNOXg}CK9SPmD? zmr-s{0wRmxUnbDrYfRvnZ@d z6johZ2sMX{YkGSKWd}m|@V7`Degt-43=2M?+jR%8{(H$&MLLmS;-|JxnX2pnz;el1jsvqQz}pGSF<`mqEXRQ5sC4#BbwnB_4` zc5bFE-Gb#JV3tox9fp-vVEN{(tOCpRse`S+@)?%pz+zVJXSooTrNCUg`R6`hxwb{) zC@{O6MKY8tfZ5@!yy=p5Y|#+myRL=^{tc(6YgAnkg3I(Cd!r5l;|;l-MQ8B`;*SCE z{u)uP^C$lOPM z5d~UhKhRRmvv{LIa^|oavk1$QiEApSrP@~Jjbg`<*dW4TO?4qG%a%sTPUFz(QtW5( zM)lA+5)0TvH~aBaOAs|}?u2FO;yc-CZ1gNM1dAxJ?%m?YsGR`}-xk2*dxC}r5j$d* zE!#Vtbo69h>V4V`BL%_&$} z+oJAo@jQ^Tk`;%xw-4G>hhb&)B?##U+(6Fi7nno`C<|#PVA%$Y{}N-?(Gc$1%tr4Pc}}hm~yY#fTOe!@v9s-ik$dX~|ygArPhByaXn8 zpI^FUjNWMsTFKTP3X7m?UK)3m zp6rI^_zxRYrx6_QmhoWoDR`fp4R7gu6;gdO)!KexaoO2D88F9x#TM1(9Bn7g;|?|o z)~$n&Lh#hCP6_LOPD>a)NmhW})LADx2kq=X7}7wYRj-0?dXr&bHaRWCfSqvzFa=sn z-8^gSyn-RmH=BZ{AJZ~!8n5621GbUJV7Qvs%JNv&$%Q17s_X%s-41vAPfIR>;x0Wlqr5?09S>x#%Qkt>?(&XjFRY}*L6BeQ3 z<6XEBh^S7>AbwGm@XP{RkeEKj6@_o%oV?hDuUpUJ+r#JZO?!IUc;r0R?>mi)*ZpQ) z#((dn=A#i_&EQn|hd)N$#A*fjBFuiHcYvo?@y1 z5|fV=a^a~d!c-%ZbMNqkMKiSzM{Yq=7_c&1H!mXk60Uv32dV;vMg&-kQ)Q{+PFtwc zj|-uQ;b^gts??J*9VxxOro}W~Q9j4Em|zSRv)(WSO9$F$s=Ydu%Q+5DOid~lwk&we zY%W(Z@ofdwPHncEZzZgmqS|!gTj3wQq9rxQy+^eNYKr1mj&?tm@wkO*9@UtnRMG>c aR{jt9+;fr}hV%pg00001^@s67{VYS000c7NklQEG_j zup^)eW&WUIApqy$=APz8jE@awGp)!bsTjDbrJO`$x^ZR^dr;>)LW>{ zs70vpsD38v)19rI=GNk1b(0?Js9~rjsQsu*K;@SD40RB-3^gKU-MYC7G!Bw{fZsqp zih4iIi;Hr_xZ033Iu{sQxLS=}yBXgLMn40d++>aQ0#%8D1EbGZp7+ z5=mK?t31BkVYbGOxE9`i748x`YgCMwL$qMsChbSGSE1`p{nSmadR zcQ#R)(?!~dmtD0+D2!K zR9%!Xp1oOJzm(vbLvT^$IKp@+W2=-}qTzTgVtQ!#Y7Gxz}stUIm<1;oBQ^Sh2X{F4ibaOOx;5ZGSNK z0maF^@(UtV$=p6DXLgRURwF95C=|U8?osGhgOED*b z7woJ_PWXBD>V-NjQAm{~T%sjyJ{5tn2f{G%?J!KRSrrGvQ1(^`YLA5B!~eycY(e5_ z*%aa{at13SxC(=7JT7$IQF~R3sy`Nn%EMv!$-8ZEAryB*yB1k&stni)=)8-ODo41g zkJu~roIgAih94tb=YsL%iH5@^b~kU9M-=aqgXIrbtxMpFy5mekFm#edF9z7RQ6V}R zBIhbXs~pMzt0VWy1Fi$^fh+1xxLDoK09&5&MJl(q#THjPm(0=z2H2Yfm^a&E)V+a5 zbi>08u;bJsDRUKR9(INSc7XyuWv(JsD+BB*0hS)FO&l&7MdViuur@-<-EHw>kHRGY zqoT}3fDv2-m{NhBG8X}+rgOEZ;amh*DqN?jEfQdqxdj08`Sr=C-KmT)qU1 z+9Cl)a1mgXxhQiHVB}l`m;-RpmKy?0*|yl?FXvJkFxuu!fKlcmz$kN(a}i*saM3nr z0!;a~_%Xqy24IxA2rz<+08=B-Q|2PT)O4;EaxP^6qixOv7-cRh?*T?zZU`{nIM-at zTKYWr9rJ=tppQ9I#Z#mLgINVB!pO-^FOcvFw6NhV0gztuO?g ztoA*C-52Q-Z-P#xB4HAY3KQVd%dz1S4PA3vHp0aa=zAO?FCt zC_GaTyVBg2F!bBr3U@Zy2iJgIAt>1sf$JWA9kh{;L+P*HfUBX1Zy{4MgNbDfBV_ly z!y#+753arsZUt@366jIC0klaC@ckuk!qu=pAyf7&QmiBUT^L1&tOHzsK)4n|pmrVT zs2($4=?s~VejTFHbFdDOwG;_58LkIj1Fh@{glkO#F1>a==ymJS$z;gdedT1zPx4Kj ztjS`y_C}%af-RtpehdQDt3a<=W5C4$)9W@QAse;WUry$WYmr51ml9lkeunUrE`-3e zmq1SgSOPNEE-Mf+AGJ$g0M;3@w!$Ej;hMh=v=I+Lpz^n%Pg^MgwyqOkNyu2c^of)C z1~ALor3}}+RiF*K4+4{(1%1j3pif1>sv0r^mTZ?5Jd-It!tfPfiG_p$AY*Vfak%FG z4z#;wLtw&E&?}w+eKG^=#jF7HQzr8rV0mY<1YAJ_uGz~$E13p?F^fPSzXSn$8UcI$ z8er9{5w5iv0qf8%70zV71T1IBB1N}R5Kp%NO0=5wJalZt8;xYp;b{1K) zHY>2wW-`Sl{=NpR%iu3(u6l&)rc%%cSA#aV7WCowfbFR4wcc{LQZv~o1u_`}EJA3>ki`?9CKYTA!rhO)if*zRdd}Kn zEPfYbhoVE~!FI_2YbC5qAj1kq;xP6%J8+?2PAs?`V3}nyFVD#sV3+uP`pi}{$l9U^ zSz}_M9f7RgnnRhaoIJgT8us!1aB&4!*vYF07Hp&}L zCRlop0oK4DL@ISz{2_BPlezc;xj2|I z23RlDNpi9LgTG_#(w%cMaS)%N`e>~1&a3<{Xy}>?WbF>OOLuO+j&hc^YohQ$4F&ze z+hwnro1puQjnKm;vFG~o>`kCeUIlkA-2tI?WBKCFLMBY=J{hpSsQ=PDtU$=duS_hq zHpymHt^uuV1q@uc4bFb{MdG*|VoW@15Osrqt2@8ll0qO=j*uOXn{M0UJX#SUztui9FN4)K3{9!y8PC-AHHvpVTU;x|-7P+taAtyglk#rjlH2 z5Gq8ik}BPaGiM{#Woyg;*&N9R2{J0V+WGB69cEtH7F?U~Kbi6ksi*`CFXsi931q7Y zGO82?whBhN%w1iDetv%~wM*Y;E^)@Vl?VDj-f*RX>{;o_=$fU!&KAXbuadYZ46Zbg z&6jMF=49$uL^73y;;N5jaHYv)BTyfh&`qVLYn?`o6BCA_z-0niZz=qPG!vonK3MW_ zo$V96zM!+kJRs{P-5-rQVse0VBH*n6A58)4uc&gfHMa{gIhV2fGf{st>E8sKyP-$8zp~wJX^A*@DI&-;8>gANXZj zU)R+Y)PB?=)a|Kj>8NXEu^S_h^7R`~Q&7*Kn!xyvzVv&^>?^iu;S~R2e-2fJx-oUb cX)(b1KSk$MOV07*qoM6N<$f&6$jw%VRuvdN2+38CZWny1cRtlsl+0_KtW)EU14Ei(F!UtWuj4IK+3{sK@>rh zs1Z;=(DD&U6+tlyL?UnHVN^&g6QhFi2#HS+*qz;(>63G(`|jRtW|nz$Pv7qTovP!^ zP_jES{mr@O-02w%!^a?^1ZP!_KmQiz0L~jZ=W@Qt`8wzOoclQsAS<5YdH;a(4bGLE zk8s}1If(PSIgVi!XE!5kA?~z*sobvNyohr;=Q_@h2@$6Flyej3J)D-6YfheRGl`HEcPk|~huT_2-U?PfL=4BPV)f1o!%rQ!NMt_MYw-5bUSwQ9Z&zC>u zOrl~UJglJNa%f50Ok}?WB{on`Ci`p^Y!xBA?m@rcJXLxtrE0FhRF3d*ir>yzO|BD$ z3V}HpFcCh6bTzY}Nt_(W%QYd3NG)jJ4<`F<1Od) zfQblTdC&h2lCz`>y?>|9o2CdvC8qZeIZt%jN;B7Hdn2l*k4M4MFEtq`q_#5?}c$b$pf_3y{Y!cRDafZBEj-*OD|gz#PBDeu3QoueOesLzB+O zxjf2wvf6Wwz>@AiOo2mO4=TkAV+g~%_n&R;)l#!cBxjuoD$aS-`IIJv7cdX%2{WT7 zOm%5rs(wqyPE^k5SIpUZ!&Lq4<~%{*>_Hu$2|~Xa;iX*tz8~G6O3uFOS?+)tWtdi| zV2b#;zRN!m@H&jd=!$7YY6_}|=!IU@=SjvGDFtL;aCtw06U;-v^0%k0FOyESt z1Wv$={b_H&8FiRV?MrzoHWd>%v6KTRU;-v^Miiz+@q`(BoT!+<37CKhoKb)|8!+RG z6BQFU^@fRW;s8!mOf2QViKQGk0TVER6EG1`#;Nm39Do^PoT!+<37AD!%oJe86(=et zZ~|sLzU>V-qYiU6V8$0GmU7_K8|Fd0B?+9Un1BhKAz#V~Fk^`mJtlCX#{^8^M8!me z8Yg;8-~>!e<-iG;h*0B1kBKm}hItVGY6WnjVpgnTTAC$rqQ^v)4KvOtpY|sIj@WYg zyw##ZZ5AC2IKNC;^hwg9BPk0wLStlmBr;E|$5GoAo$&Ui_;S9WY62n3)i49|T%C#i017z3J=$RF|KyZWnci*@lW4 z=AKhNN6+m`Q!V3Ye68|8y@%=am>YD0nG99M)NWc20%)gwO!96j7muR}Fr&54SxKP2 zP30S~lt=a*qDlbu3+Av57=9v&vr<6g0&`!8E2fq>I|EJGKs}t|{h7+KT@)LfIV-3K zK)r_fr2?}FFyn*MYoLC>oV-J~eavL2ho4a4^r{E-8m2hi>~hA?_vIG4a*KT;2eyl1 zh_hUvUJpNCFwBvRq5BI*srSle>c6%n`#VNsyC|MGa{(P&08p=C9+WUw9Hl<1o9T4M zdD=_C0F7#o8A_bRR?sFNmU0R6tW`ElnF8p53IdHo#S9(JoZCz}fHwJ6F<&?qrpVqE zte|m%89JQD+XwaPU#%#lVs-@-OL);|MdfINd6!XwP2h(eyafTUsoRkA%&@fe?9m@jw-v(yTTiV2(*fthQH9}SqmsRPVnwwbV$1E(_lkmo&S zF-truCU914_$jpqjr(>Ha4HkM4YMT>m~NosUu&UZ>zirfHo%N6PPs9^_o$WqPA0#5 z%tG>qFCL+b*0s?sZ;Sht0nE7Kl>OVXy=gjWxxK;OJ3yGd7-pZf7JYNcZo2*1SF`u6 zHJyRRxGw9mDlOiXqVMsNe#WX`fC`vrtjSQ%KmLcl(lC>ZOQzG^%iql2w-f_K@r?OE zwCICifM#L-HJyc7Gm>Ern?+Sk3&|Khmu4(~3qa$(m6Ub^U0E5RHq49za|XklN#?kP zl;EstdW?(_4D>kwjWy2f!LM)y?F94kyU3`W!6+AyId-89v}sXJpuic^NLL7GJItl~ zsiuB98AI-(#Mnm|=A-R6&2fwJ0JVSY#Q>&3$zFh|@;#%0qeF=j5Ajq@4i0tIIW z&}sk$&fGwoJpe&u-JeGLi^r?dO`m=y(QO{@h zQqAC7$rvz&5+mo3IqE?h=a~6m>%r5Quapvzq;{y~p zJpyXOBgD9VrW7@#p6l7O?o3feml(DtSL>D^R) zZUY%T2b0-vBAFN7VB;M88!~HuOXi4KcI6aRQ&h|XQ0A?m%j2=l1f0cGP}h(oVfJ`N zz#PpmFC*ieab)zJK<4?^k=g%OjPnkANzbAbmGZHoVRk*mTfm75s_cWVa`l*f$B@xu z5E*?&@seIo#*Y~1rBm!7sF9~~u6Wrj5oICUOuz}CS)jdNIznfzCA(stJ(7$c^e5wN z?lt>eYgbA!kvAR7zYSD&*r1$b|(@;9dcZ^67R0 zXAXJKa|5Sdmj!g578Nwt6d$sXuc&MWezA0Whd`94$h{{?1IwXP4)Tx4obDK%xoFZ_Z zjjHJ_P@R_e5blG@yEjnaJb`l;s%Lb2&=8$&Ct-fV`E^4CUs)=jTk!I}2d&n!f@)bm z@ z_4Dc86+3l2*p|~;o-Sb~oXb_RuLmoifDU^&Te$*FevycC0*nE3Xws8gsWp|Rj2>SM zns)qcYj?^2sd8?N!_w~4v+f-HCF|a$TNZDoNl$I1Uq87euoNgKb6&r26TNrfkUa@o zfdiFA@p{K&mH3b8i!lcoz)V{n8Q@g(vR4ns4r6w;K z>1~ecQR0-<^J|Ndg5fvVUM9g;lbu-){#ghGw(fg>L zh)T5Ljb%lWE;V9L!;Cqk>AV1(rULYF07ZBJbGb9qbSoLAd;in9{)95YqX$J43-dY7YU*k~vrM25 zxh5_IqO0LYZW%oxQ5HOzmk4x{atE*vipUk}sh88$b2tn?!ujEHn`tQLe&vo}nMb&{ zio`xzZ&GG6&ZyN3jnaQy#iVqXE9VT(3tWY$n-)uWDQ|tc{`?fq2F`oQ{;d3aWPg4Hp-(iE{ry>MIPWL> iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/analytics/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/analytics/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/analytics/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/analytics/example/ios/Runner/Base.lproj/Main.storyboard b/packages/analytics/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/analytics/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/analytics/example/ios/Runner/Info.plist b/packages/analytics/example/ios/Runner/Info.plist new file mode 100644 index 000000000..7f553465b --- /dev/null +++ b/packages/analytics/example/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/packages/analytics/example/ios/Runner/Runner-Bridging-Header.h b/packages/analytics/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/analytics/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/analytics/example/lib/main.dart b/packages/analytics/example/lib/main.dart new file mode 100644 index 000000000..7412eae85 --- /dev/null +++ b/packages/analytics/example/lib/main.dart @@ -0,0 +1,83 @@ +import 'package:analytics/sdk/datadog/core/datadog_configuration.dart'; +import 'package:analytics/sdk/datadog/core/enums.dart'; +import 'package:analytics/sdk/datadog/sdk/deriv_datadog.dart'; +import 'package:analytics/sdk/rudderstack/core/rudderstack_configuration.dart'; +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:deriv_env/env.dart'; +import 'package:example/pages/rudderstack.dart'; +import 'package:flutter/material.dart'; + + +import 'package:example/pages/first_page.dart'; +import 'package:example/pages/second_page.dart'; +import 'package:example/pages/splash_screen.dart'; + +void main() async { + + WidgetsFlutterBinding.ensureInitialized(); + + await Env().load(); + + DerivDatadogConfiguration configuration = DerivDatadogConfiguration( + applicationId: Env().get( + 'DATADOG_APPLICATION_ID', + ), + clientToken: Env().get( + 'DATADOG_CLIENT_TOKEN', + ), + env: Env().get( + 'DATADOG_ENVIRONMENT', + ), + serviceName: Env().get( + 'DATADOG_SERVICE_NAME', + ), + trackingConsent: TrackingConsent.granted, + ); + + DerivDatadog().setup(configuration); + + DerivRudderstack().setup(RudderstackConfiguration( + dataPlaneUrl: Env().get( + 'RUDDERSTACK_DATA_PLANE_URL' + ), + writeKey: Env().get( + 'RUDDERSTACK_WRITE_KEY', + ), + )); + + runApp(const App()); +} + +class App extends StatefulWidget { + const App({Key? key}) : super(key: key); + + @override + State createState() => _AppState(); +} + +class _AppState extends State { + @override + void initState() { + super.initState(); + + DerivDatadog().setUserInfo( + id: "0", + name: "Example App User", + email: "example_user@deriv.com", + extraInfo: {}, + ); + } + + // This widget is the root of your application. + @override + Widget build(BuildContext context) => MaterialApp( + navigatorObservers: [DerivDatadog().navigatorObserver], + initialRoute: '/splash_screen', + routes: { + '/splash_screen': (context) => const SplashScreen(), + '/': (context) => const FirstPage(), + '/second': (context) => const SecondPage(), + '/rudderstack': (context) => const RudderStack(), + }, + ); +} diff --git a/packages/analytics/example/lib/pages/first_page.dart b/packages/analytics/example/lib/pages/first_page.dart new file mode 100644 index 000000000..9cd47ea61 --- /dev/null +++ b/packages/analytics/example/lib/pages/first_page.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class FirstPage extends StatefulWidget { + const FirstPage({Key? key}) : super(key: key); + + @override + State createState() => _FirstPageState(); +} + +class _FirstPageState extends State { + @override + Widget build(BuildContext context) => Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("DataDog Test App 1"), + TextButton( + onPressed: () { + Navigator.pushNamed(context, "/second"); + }, + child: const Text("Test Button 1"), + ), + ], + ), + ), + ); +} diff --git a/packages/analytics/example/lib/pages/rudderstack.dart b/packages/analytics/example/lib/pages/rudderstack.dart new file mode 100644 index 000000000..eccb7df3e --- /dev/null +++ b/packages/analytics/example/lib/pages/rudderstack.dart @@ -0,0 +1,175 @@ +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:flutter/material.dart'; + +class RudderStack extends StatefulWidget { + // ignore: public_member_api_docs + const RudderStack({super.key}); + + @override + _RudderStackState createState() => _RudderStackState(); +} + +class _RudderStackState extends State { + bool _enabled = false; + + @override + void initState() { + super.initState(); + + + } + + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Rudderstack example app'), + //back button + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context, false), + ), + ), + body: SingleChildScrollView( + child: Builder( + builder: (BuildContext context) => Column( + children: [ + _enableController(context), + const Divider(), + GridView.count( + shrinkWrap: true, + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + padding: const EdgeInsets.all(16), + children: _eventsList(context), + ), + ], + ), + ), + ), + ), + ); + + List _eventsList(BuildContext context) => [ + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.teal[100], + child: const Center( + child: Text( + 'Identify', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + await DerivRudderstack() + .setContext(token: 'xxx-xxxx-xxxx-xxxxx-xxxx-test'); + final bool result = + await DerivRudderstack().identify(userId: '998'); + _showSnackBar(context, result); + }, + ), + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.blue[100], + child: const Center( + child: Text( + 'Track', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + final bool result = await DerivRudderstack().track( + eventName: 'Application Opened' + ); + + _showSnackBar(context, result); + }), + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.orange[100], + child: const Center( + child: Text( + 'Screen', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + final bool result = await DerivRudderstack().screen( + screenName: 'main' + ); + + _showSnackBar(context, result); + }), + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.indigo[100], + child: const Center( + child: Text( + 'group', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + final bool result = await DerivRudderstack().group( + groupId: 'Group-id-test' + ); + _showSnackBar(context, result); + }), + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.purple[100], + child: const Center( + child: Text( + 'alias', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + final bool result = + await DerivRudderstack().alias(alias: 'Alias-test'); + _showSnackBar(context, result); + }), + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.deepOrange[100], + child: const Center( + child: Text( + 'reset', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + final bool result = await DerivRudderstack().reset(); + _showSnackBar(context, result); + }), + ]; + + Widget _enableController(BuildContext context) => SwitchListTile( + title: const Text('Enable RudderStack'), + value: _enabled, + onChanged: (bool newValue) async { + setState(() => _enabled = newValue); + + bool result; + if (_enabled) { + result = await DerivRudderstack().enable(); + } else { + result = await DerivRudderstack().disable(); + } + + _showSnackBar(context, result); + }, + ); + + void _showSnackBar(BuildContext context, bool success) => + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(success ? 'Success' : 'Failure')), + ); +} diff --git a/packages/analytics/example/lib/pages/second_page.dart b/packages/analytics/example/lib/pages/second_page.dart new file mode 100644 index 000000000..572587e89 --- /dev/null +++ b/packages/analytics/example/lib/pages/second_page.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +class SecondPage extends StatefulWidget { + const SecondPage({Key? key}) : super(key: key); + + @override + State createState() => _SecondPageState(); +} + +class _SecondPageState extends State { + int count = 0; + + @override + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + setState(() => count = 0); + + return true; + }, + child: Scaffold( + appBar: AppBar( + elevation: 0, + backgroundColor: Colors.transparent, + iconTheme: const IconThemeData(color: Colors.black), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("DataDog Test App 2"), + if (count > 0 && count < 5) + const Text("This button doesn't do anything."), + if (count >= 5 && count < 10) + const Text( + "STOP CLICKING THE BUTTON IT DOESN'T DO ANYTHING!"), + if (count >= 10) + const Text( + "There is an error! Happy now?!", + style: TextStyle(color: Colors.red), + ), + TextButton( + onPressed: () { + setState(() => count++); + + Navigator.pushNamed(context, "/rudderstack"); + }, + child: const Text("RudderStack"), + ), + ], + ), + ), + ), + ); +} diff --git a/packages/analytics/example/lib/pages/splash_screen.dart b/packages/analytics/example/lib/pages/splash_screen.dart new file mode 100644 index 000000000..9417ad2e5 --- /dev/null +++ b/packages/analytics/example/lib/pages/splash_screen.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +class SplashScreen extends StatefulWidget { + const SplashScreen({Key? key}) : super(key: key); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State { + @override + void initState() { + super.initState(); + + Future.delayed( + const Duration(seconds: 2), + () => Navigator.pushNamed(context, "/"), + ); + } + + @override + Widget build(BuildContext context) => const Scaffold( + body: SizedBox( + height: double.infinity, + width: double.infinity, + child: Center( + child: Hero( + tag: "datadog_logo", + child: Text( + "Analytics Test App", + ), + ), + ), + ), + ); +} diff --git a/packages/analytics/example/pubspec.yaml b/packages/analytics/example/pubspec.yaml new file mode 100644 index 000000000..98493558a --- /dev/null +++ b/packages/analytics/example/pubspec.yaml @@ -0,0 +1,38 @@ +name: example +description: A new Flutter project. + +publish_to: "none" + +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: "3.10.2" + +dependencies: + flutter: + sdk: flutter + deriv_env: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_env + ref: dev + + analytics: + path: ../ + + cupertino_icons: ^1.0.2 + +dependency_overrides: + + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^1.0.0 + +flutter: + uses-material-design: true + assets: + - .env diff --git a/packages/analytics/lib/analytics.dart b/packages/analytics/lib/analytics.dart index 1aade9933..22574f33a 100644 --- a/packages/analytics/lib/analytics.dart +++ b/packages/analytics/lib/analytics.dart @@ -1,17 +1,21 @@ -import 'dart:io'; - +import 'package:analytics/sdk/firebase/core/firebase_configuration.dart'; +import 'package:analytics/sdk/firebase/sdk/deriv_firebase_analytics.dart'; +import 'package:analytics/sdk/rudderstack/core/rudderstack_configuration.dart'; +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; -import 'package:deriv_rudderstack/deriv_rudderstack.dart'; - import 'analytics_route_observer.dart'; /// Class that collects and send analytical information to `Firebase` and /// `RudderStack`. +@Deprecated( + 'Use DerivFirebaseAnalytics, DerivRudderstack, or DerivDatadog instead.') class Analytics { /// Initialises + @Deprecated( + 'Use DerivFirebaseAnalytics, DerivRudderstack, or DerivDatadog instead.') factory Analytics() => _instance; Analytics._internal(); @@ -20,29 +24,38 @@ class Analytics { static final Analytics _instance = Analytics._internal(); /// Contains ignored routes/screen names. + @Deprecated('No need to have it here as it can be done in the client app.') List ignoredRoutes = []; - late FirebaseAnalytics _firebaseAnalytics; + late DerivFirebaseAnalytics _derivFirebaseAnalytics; /// An instance of custom route observer created for analytics. + @Deprecated( + "Use the 'navigatorObserver' in DerivFirebaseAnalytics, DerivRudderstack, or DerivDatadog instead.") late AnalyticsRouteObserver observer; /// Initialises the `Analytics`. /// Sets the device-token to `RudderStack`. /// bool [isEnabled] enables or disables "Analytics". - Future init({ - required bool isEnabled, - required FirebaseApp firebaseApp, - }) async { - _firebaseAnalytics = FirebaseAnalytics.instanceFor(app: firebaseApp); + @Deprecated( + "Use the 'setup' function in DerivFirebaseAnalytics, DerivRudderstack, or DerivDatadog instead.") + Future init( + {required bool isEnabled, + required FirebaseApp firebaseApp, + RudderstackConfiguration? configuration}) async { + _derivFirebaseAnalytics = + DerivFirebaseAnalytics(FirebaseAnalytics.instanceFor(app: firebaseApp)); observer = AnalyticsRouteObserver(onNewRoute: _newRouteHandler); // Enable or disable the analytics on this device. - await _firebaseAnalytics.setAnalyticsCollectionEnabled(isEnabled); + await _derivFirebaseAnalytics.setup( + FirebaseConfiguration( + isAnalyticsCollectionEnabled: isEnabled, + ), + ); - // For ios we have to manually setup the rudderStack as it's not get initialized with register method. - if (Platform.isIOS) { - await setupRudderStackForIos(); + if (configuration != null) { + await DerivRudderstack().setup(configuration); } isEnabled @@ -52,52 +65,45 @@ class Analytics { /// Captures `screen_view` event on route changes. void _newRouteHandler(PageRoute route) { + // ignore: deprecated_member_use_from_same_package setCurrentScreen( screenName: route.settings.name ?? '', - // ignore: avoid_as - properties: route.settings.arguments as Map? ?? - {}, ); } - /// Captures `app_open` event when the app is opened. - void logAppOpened() { - _firebaseAnalytics.logAppOpen(); - - DerivRudderstack().track(eventName: 'Application Opened'); - } - /// Captures `Application Backgrounded` event when the app goes to background. + @Deprecated("Use the 'track' function in DerivRudderstack instead.") void logAppBackgrounded() { DerivRudderstack().track(eventName: 'Application Backgrounded'); } /// Captures `Application Crashed` event when the app is crashed. + @Deprecated("Use the 'track' function in DerivRudderstack instead.") void logAppCrashed() { DerivRudderstack().track(eventName: 'Application Crashed'); } /// Captures information about current screen in use. + @Deprecated( + "Use the 'setCurrentScreen' function in DerivFirebaseAnalytics or the 'screen' function DerivRudderstack instead.") void setCurrentScreen({ required String screenName, - Map properties = const {}, }) { if (ignoredRoutes.contains(screenName)) { return; } - _firebaseAnalytics.setCurrentScreen(screenName: screenName); + _derivFirebaseAnalytics.setCurrentScreen(screenName: screenName); - DerivRudderstack().screen( - screenName: screenName, - properties: properties, - ); + DerivRudderstack().screen(screenName: screenName); } /// Captures `login` event upon a successful user log in. + @Deprecated( + "Use the 'logLoginEvent' function in DerivFirebaseAnalytics or the 'identify' function in DerivRudderstack instead.") Future logLoginEvent( {required String deviceToken, required int userId}) async { await _setFirebaseUserId(userId.toString()); - await _firebaseAnalytics.logLogin(); + await _derivFirebaseAnalytics.logLogin(); await _setRudderStackDeviceToken(deviceToken); @@ -105,8 +111,10 @@ class Analytics { } /// Captures `logout` event when the user logs out. + @Deprecated( + "Use the 'logLogoutEvent' function in DerivFirebaseAnalytics instead.") void logLogoutEvent() { - _firebaseAnalytics.logEvent(name: 'logout'); + _derivFirebaseAnalytics.logEvent(name: 'logout'); } /// Sets the device-token to `RudderStack`. @@ -115,27 +123,25 @@ class Analytics { /// Sets the user id for `Firebase`. Future _setFirebaseUserId(String userId) => - _firebaseAnalytics.setUserId(id: userId); + _derivFirebaseAnalytics.setUserId(id: userId); /// Logs push token. + @Deprecated("Use the 'setContext' function in DerivRudderstack instead.") Future logPushToken(String deviceToken) async { await _setRudderStackDeviceToken(deviceToken); } - /// This method initialize the rudderStack client for ios. - Future setupRudderStackForIos() async { - await DerivRudderstack().setup(); - } - /// Should be called at logout to clear up current `RudderStack` data. + @Deprecated("Use the 'reset' function in DerivRudderstack instead.") Future reset() async => DerivRudderstack().reset(); /// Logs custom events to `Firebase`. + @Deprecated("Use the 'logEvent' function in DerivFirebaseAnalytics instead.") Future logToFirebase({ required String name, Map? params, }) => - _firebaseAnalytics.logEvent( + _derivFirebaseAnalytics.logEvent( name: name, parameters: params, ); diff --git a/packages/analytics/lib/core/logger.dart b/packages/analytics/lib/core/logger.dart new file mode 100644 index 000000000..20e366259 --- /dev/null +++ b/packages/analytics/lib/core/logger.dart @@ -0,0 +1,15 @@ +import 'dart:developer' as logger_dev; + +/// Logger interface for logging errors or messages +abstract class Logger { + /// Logs a [message]. + void log(String message); +} + +/// Concrete implementation of [Logger]. +class ConsoleLogger implements Logger { + @override + void log(String message) { + logger_dev.log(message); + } +} diff --git a/packages/analytics/lib/sdk/base_analytics.dart b/packages/analytics/lib/sdk/base_analytics.dart new file mode 100644 index 000000000..18041e9ca --- /dev/null +++ b/packages/analytics/lib/sdk/base_analytics.dart @@ -0,0 +1,11 @@ +import 'package:analytics/sdk/base_analytics_configuration.dart'; +import 'package:flutter/widgets.dart'; + +/// Define the analytics interface +abstract class BaseAnalytics { + /// The [NavigatorObserver] instances used for tracking navigation events. + NavigatorObserver get navigatorObserver; + + /// Sets up the analytics client. + Future setup(T configuration); +} diff --git a/packages/analytics/lib/sdk/base_analytics_configuration.dart b/packages/analytics/lib/sdk/base_analytics_configuration.dart new file mode 100644 index 000000000..79438847f --- /dev/null +++ b/packages/analytics/lib/sdk/base_analytics_configuration.dart @@ -0,0 +1,2 @@ +/// Abstract base class that defines the structure for all analytics configurations. +abstract class BaseAnalyticsConfiguration {} diff --git a/packages/analytics/lib/sdk/datadog/core/datadog_configuration.dart b/packages/analytics/lib/sdk/datadog/core/datadog_configuration.dart new file mode 100644 index 000000000..66f5a8d20 --- /dev/null +++ b/packages/analytics/lib/sdk/datadog/core/datadog_configuration.dart @@ -0,0 +1,45 @@ +import 'package:analytics/sdk/base_analytics_configuration.dart'; +import 'package:analytics/sdk/datadog/core/enums.dart'; + +/// A class to define the configuration options for the [DerivDatadog]. +class DerivDatadogConfiguration implements BaseAnalyticsConfiguration { + /// Creates a new [DerivDatadogConfiguration] instance with the given options. + const DerivDatadogConfiguration({ + required this.applicationId, + required this.clientToken, + required this.env, + required this.trackingConsent, + this.site = DatadogSite.us1, + this.nativeCrashReportEnabled = true, + this.sessionSamplingRate = 100, + this.tracingSamplingRate = 100, + this.serviceName, + }); + + /// The application id used to identify the app in the `Datadog` dashboard. + final String applicationId; + + /// The client token used to authenticate with the `Datadog API`. + final String clientToken; + + /// The environment in which the SDK is running. + final String env; + + /// The `Datadog` site to use. + final DatadogSite? site; + + /// The user's tracking consent status. + final TrackingConsent trackingConsent; + + /// Whether native crash reporting is enabled. + final bool? nativeCrashReportEnabled; + + /// The sampling rate for sessions. + final double? sessionSamplingRate; + + /// The sampling rate for resource traces. + final double? tracingSamplingRate; + + /// The service name for this application + final String? serviceName; +} diff --git a/packages/analytics/lib/sdk/datadog/core/enums.dart b/packages/analytics/lib/sdk/datadog/core/enums.dart new file mode 100644 index 000000000..20f4d4e46 --- /dev/null +++ b/packages/analytics/lib/sdk/datadog/core/enums.dart @@ -0,0 +1,108 @@ +import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart' as datadog; + +/// An enum to represent the user's tracking consent status, used in [DatadogSdkConfig]. +enum TrackingConsent { + /// user has granted tracking consent + granted, + + /// user has not granted tracking consent + notGranted, + + /// user's tracking consent is pending + pending, +} + +/// A Extension on [TrackingConsent]. +extension TrackingConsentExtension on TrackingConsent { + /// The consent getter method is an extension to the [datadog.TrackingConsent] + /// enum that returns the corresponding [datadog.TrackingConsent] value for + /// the given [TrackingConsent] value. + datadog.TrackingConsent get consent => trackingConsentMapper[this]!; +} + +/// A mapper class to map [TrackingConsent] to [datadog.TrackingConsent]. +Map trackingConsentMapper = + { + TrackingConsent.granted: datadog.TrackingConsent.granted, + TrackingConsent.notGranted: datadog.TrackingConsent.notGranted, + TrackingConsent.pending: datadog.TrackingConsent.pending, +}; + +/// Determines the server for uploading RUM events. +enum DatadogSite { + /// US based servers. Sends RUM events to + /// [app.datadoghq.com](https://app.datadoghq.com/). + us1, + + /// US based servers. Sends RUM events to + /// [us3.datadoghq.com](https://us3.datadoghq.com/). + us3, + + /// US based servers. Sends RUM events to + /// [us5.datadoghq.com](https://us5.datadoghq.com/). + us5, + + /// Europe based servers. Sends RUM events to + /// [app.datadoghq.eu](https://app.datadoghq.eu/). + eu1, + + /// US based servers, FedRAMP compatible. Sends RUM events to + /// [app.ddog-gov.com](https://app.ddog-gov.com/). + us1Fed, + + /// Asia baesd servers. Sends data to + /// [ap1.datadoghq.com](https://ap1.datadoghq.com). + ap1, +} + +/// A Extension on [DatadogSite]. +extension DatadogSiteExtension on DatadogSite { + /// The consent getter method is an extension to the [datadog.DatadogSite] + /// enum that returns the corresponding [datadog.DatadogSite] value for + /// the given [DatadogSite] value. + datadog.DatadogSite get site => siteMapper[this] ?? datadog.DatadogSite.us1; +} + +/// A mapper class to map [DatadogSite] to [datadog.DatadogSite]. +Map siteMapper = + { + DatadogSite.us1: datadog.DatadogSite.us1, + DatadogSite.us3: datadog.DatadogSite.us3, + DatadogSite.us5: datadog.DatadogSite.us5, + DatadogSite.eu1: datadog.DatadogSite.eu1, + DatadogSite.us1Fed: datadog.DatadogSite.us1Fed, + DatadogSite.ap1: datadog.DatadogSite.ap1, +}; + +/// An enum to represent the user's tracking consent status, used in [DatadogSdkConfig]. +enum RumUserActionType { + /// tap action + tap, + + /// scroll action + scroll, + + /// swipe action + swipe, + + /// custom action + custom +} + +/// A mapper class to map [RumUserActionType] to [datadog.RumUserActionType]. +Map rumUserActionTypeMapper = + { + RumUserActionType.tap: datadog.RumUserActionType.tap, + RumUserActionType.scroll: datadog.RumUserActionType.scroll, + RumUserActionType.swipe: datadog.RumUserActionType.swipe, + RumUserActionType.custom: datadog.RumUserActionType.custom, +}; + +/// Extension on [RumUserActionType]. +extension RumUserActionTypeExtension on RumUserActionType { + /// The consent getter method is an extension to the [datadog.RumUserActionType] + /// enum that returns the corresponding [datadog.RumUserActionType] value for + /// the given [RumUserActionType] value. + datadog.RumUserActionType get rumUserActionType => + rumUserActionTypeMapper[this]!; +} diff --git a/packages/analytics/lib/sdk/datadog/sdk/deriv_datadog.dart b/packages/analytics/lib/sdk/datadog/sdk/deriv_datadog.dart new file mode 100644 index 000000000..e3d91efff --- /dev/null +++ b/packages/analytics/lib/sdk/datadog/sdk/deriv_datadog.dart @@ -0,0 +1,109 @@ +import 'dart:ui'; + +import 'package:analytics/sdk/base_analytics.dart'; +import 'package:analytics/sdk/datadog/core/datadog_configuration.dart'; +import 'package:analytics/sdk/datadog/core/enums.dart'; +import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart' as datadog; +import 'package:datadog_flutter_plugin/datadog_internal.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +/// Implement Datadog +class DerivDatadog implements BaseAnalytics { + /// Returns the singleton instance of the [DerivDatadog]. + factory DerivDatadog() => _instance ??= DerivDatadog._(); + + DerivDatadog._(); + + static DerivDatadog? _instance; + + datadog.DatadogSdk _datadogSDK = datadog.DatadogSdk.instance; + + /// Returns navigation observer for the [Datadog]. + datadog.DatadogNavigationObserver get _navigationObserver => + datadog.DatadogNavigationObserver(datadogSdk: _datadogSDK); + + @override + NavigatorObserver get navigatorObserver => _navigationObserver; + + /// Sets the [rudderClient] instance in the case of testing. + @visibleForTesting + // ignore: use_setters_to_change_properties + void setDatadogSdk(datadog.DatadogSdk rudderClient) { + _datadogSDK = rudderClient; + } + + @override + Future setup(DerivDatadogConfiguration configuration) async { + try { + final datadog.RumConfiguration rumConfiguration = + datadog.RumConfiguration( + applicationId: configuration.applicationId, + sessionSamplingRate: configuration.sessionSamplingRate ?? 100, + tracingSamplingRate: configuration.tracingSamplingRate ?? 100, + ); + + final datadog.DdSdkConfiguration datadogConfiguration = + datadog.DdSdkConfiguration( + clientToken: configuration.clientToken, + env: configuration.env, + serviceName: configuration.serviceName, + site: configuration.site?.site ?? DatadogSite.us1.site, + trackingConsent: configuration.trackingConsent.consent, + nativeCrashReportEnabled: + configuration.nativeCrashReportEnabled ?? true, + loggingConfiguration: datadog.LoggingConfiguration(), + rumConfiguration: rumConfiguration, + ); + + final FlutterExceptionHandler? originalOnError = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + _datadogSDK.rum?.handleFlutterError(details); + originalOnError?.call(details); + }; + final ErrorCallback? platformOriginalOnError = + PlatformDispatcher.instance.onError; + PlatformDispatcher.instance.onError = (Object e, StackTrace st) { + _datadogSDK.rum?.addErrorInfo( + e.toString(), + datadog.RumErrorSource.source, + stackTrace: st, + ); + return platformOriginalOnError?.call(e, st) ?? false; + }; + + await _datadogSDK.initialize(datadogConfiguration); + _datadogSDK.updateConfigurationInfo( + LateConfigurationProperty.trackErrors, true); + return true; + } on Exception { + return false; + } + } + + /// Sets the user information for the current session. + void setUserInfo({ + String? id, + String? name, + String? email, + Map extraInfo = const {}, + }) => + _datadogSDK.setUserInfo( + id: id, + name: name, + email: email, + extraInfo: extraInfo, + ); + + /// Logs Custom Event + void logEvent({ + required RumUserActionType type, + required String name, + Map attributes = const {}, + }) => + _datadogSDK.rum?.addUserAction( + type.rumUserActionType, + name, + attributes, + ); +} diff --git a/packages/analytics/lib/sdk/firebase/core/firebase_configuration.dart b/packages/analytics/lib/sdk/firebase/core/firebase_configuration.dart new file mode 100644 index 000000000..7862d7642 --- /dev/null +++ b/packages/analytics/lib/sdk/firebase/core/firebase_configuration.dart @@ -0,0 +1,12 @@ +import 'package:analytics/sdk/base_analytics_configuration.dart'; + +/// A class to define the configuration options for [FirebaseConfiguration]. +class FirebaseConfiguration implements BaseAnalyticsConfiguration { + /// Creates a new [FirebaseConfiguration] instance with the given options. + const FirebaseConfiguration({required this.isAnalyticsCollectionEnabled}); + + /// Sets whether analytics collection is enabled for this app on this device. + /// + /// This setting is persisted across app sessions. + final bool isAnalyticsCollectionEnabled; +} diff --git a/packages/analytics/lib/sdk/firebase/sdk/deriv_firebase_analytics.dart b/packages/analytics/lib/sdk/firebase/sdk/deriv_firebase_analytics.dart new file mode 100644 index 000000000..9b840e31f --- /dev/null +++ b/packages/analytics/lib/sdk/firebase/sdk/deriv_firebase_analytics.dart @@ -0,0 +1,105 @@ +import 'dart:async'; + +import 'package:analytics/analytics_route_observer.dart'; +import 'package:analytics/core/logger.dart'; +import 'package:analytics/sdk/base_analytics.dart'; +import 'package:analytics/sdk/firebase/core/firebase_configuration.dart'; +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:flutter/widgets.dart'; + +/// A wrapper around Firebase Flutter SDK. +class DerivFirebaseAnalytics implements BaseAnalytics { + /// Creates a new [DerivFirebaseAnalytics] instance. + factory DerivFirebaseAnalytics(FirebaseAnalytics firebaseAnalytics) { + _instance._firebaseAnalytics = firebaseAnalytics; + return _instance; + } + + DerivFirebaseAnalytics._internal(); + + static final DerivFirebaseAnalytics _instance = + DerivFirebaseAnalytics._internal(); + + @override + NavigatorObserver get navigatorObserver => + AnalyticsRouteObserver(onNewRoute: (Route route) { + if (route.settings.name != null) { + setCurrentScreen( + screenName: route.settings.name!, + ); + } + }); + + late FirebaseAnalytics _firebaseAnalytics; + + final Logger _logger = ConsoleLogger(); + + /// Sets up the Firebase client. + /// + /// This method must be called before any other method. + @override + Future setup(FirebaseConfiguration configuration) => _execute(() async { + await _firebaseAnalytics.setAnalyticsCollectionEnabled( + configuration.isAnalyticsCollectionEnabled); + }); + + /// Logs the passed [screenName] to firebase analytics. + /// [screenName] is required and the method returns true if logging completed + /// Successfully, otherwise, a false is returned. + Future setCurrentScreen({required String screenName}) async => + _execute(() async { + await _firebaseAnalytics.setCurrentScreen(screenName: screenName); + }); + + /// Logs the standard login event. + /// + /// Apps with a login feature can report this event to signify that a user has logged in. + Future logLogin({ + String? loginMethod, + }) async => + _execute(() async { + await _firebaseAnalytics.logLogin(loginMethod: loginMethod); + }); + + /// Logs a custom Flutter Analytics event with the given [name] and event [parameters]. + Future logEvent({ + required String name, + Map? parameters, + }) async => + _execute(() async { + await _firebaseAnalytics.logEvent( + name: name, + parameters: parameters, + ); + }); + + /// Sets the user ID property. + /// + /// Setting a null [id] removes the user id. + Future setUserId({ + required String id, + }) async => + _execute(() async { + await _firebaseAnalytics.setUserId(id: id); + }); + + /// Sets a user property to a given value. + Future setUserProperty({ + required String name, + required String value, + }) async => + _execute(() async { + await _firebaseAnalytics.setUserProperty(name: name, value: value); + }); + + /// Executes [action] and logs errors, if any. + Future _execute(Function action) async { + try { + action(); + return true; + } on Exception catch (e) { + _logger.log('DerivFirebase: $e'); + return false; + } + } +} diff --git a/packages/analytics/lib/sdk/rudderstack/core/rudderstack_configuration.dart b/packages/analytics/lib/sdk/rudderstack/core/rudderstack_configuration.dart new file mode 100644 index 000000000..4465aef12 --- /dev/null +++ b/packages/analytics/lib/sdk/rudderstack/core/rudderstack_configuration.dart @@ -0,0 +1,20 @@ +import 'package:analytics/sdk/base_analytics_configuration.dart'; + +/// A class to define the configuration options for [RudderstackConfiguration]. +class RudderstackConfiguration implements BaseAnalyticsConfiguration { + /// Creates a new [RudderstackConfiguration] instance with the given options. + const RudderstackConfiguration({ + required this.dataPlaneUrl, + required this.writeKey, + this.debugEnabled = false, + }); + + /// The data plane url used to identify the app in the `Rudderstack` dashboard. + final String dataPlaneUrl; + + /// The write key used to authenticate with the `Rudderstack API`. + final String writeKey; + + /// Whether to enable debug mode or not. + final bool debugEnabled; +} diff --git a/packages/analytics/lib/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart b/packages/analytics/lib/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart new file mode 100644 index 000000000..06f366130 --- /dev/null +++ b/packages/analytics/lib/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart @@ -0,0 +1,109 @@ +import 'dart:async'; + +import 'package:analytics/analytics_route_observer.dart'; +import 'package:analytics/core/logger.dart'; +import 'package:analytics/sdk/base_analytics.dart'; +import 'package:analytics/sdk/rudderstack/core/rudderstack_configuration.dart'; +import 'package:flutter/widgets.dart'; +import 'package:rudder_sdk_flutter_platform_interface/platform.dart'; +import 'package:rudder_sdk_flutter/RudderController.dart'; + +/// A wrapper around RudderStack Flutter SDK. +class DerivRudderstack implements BaseAnalytics { + /// Creates a new [DerivRudderstack] instance. + factory DerivRudderstack() => _instance; + + DerivRudderstack._internal(); + + static final DerivRudderstack _instance = DerivRudderstack._internal(); + + @override + NavigatorObserver get navigatorObserver => + AnalyticsRouteObserver(onNewRoute: (Route route) { + if (route.settings.name != null) { + screen( + screenName: route.settings.name!, + ); + } + }); + + RudderController _rudderClient = RudderController.instance; + Logger _logger = ConsoleLogger(); + + /// The [RudderController] instance used for tracking events. + RudderController get rudderClient => _rudderClient; + + /// The [Logger] instance used for logging messages and errors. + Logger get logger => _logger; + + /// Sets the [rudderClient] instance in the case of testing. + @visibleForTesting + set rudderClient(RudderController rudderClient) { + _rudderClient = rudderClient; + } + + /// Sets the [logger] instance in the case of testing. + @visibleForTesting + set logger(Logger logger) { + _logger = logger; + } + + /// Sets the user id for this instance. + Future identify({required String userId}) async => + _execute(() => rudderClient.identify(userId)); + + /// Tracks an event with the given [eventName]. + Future track({required String eventName}) async => + _execute(() => rudderClient.track(eventName)); + + /// Logs a screen view with the given [screenName]. + Future screen({required String screenName}) async => + _execute(() => rudderClient.screen(screenName)); + + /// Adds a user to a group with the given [groupId]. + Future group({required String groupId}) async => + _execute(() => rudderClient.group(groupId)); + + /// Aliases a user with the given alias. + Future alias({required String alias}) async => + _execute(() => rudderClient.alias(alias)); + + /// Sets up the RudderStack client. + /// + /// Takes [dataPlaneUrl] and [writeKey] as parameters. + @override + Future setup(RudderstackConfiguration configuration) async => + _execute(() { + final RudderConfigBuilder builder = RudderConfigBuilder() + ..withDataPlaneUrl(configuration.dataPlaneUrl) + ..withDebug(configuration.debugEnabled); + rudderClient.initialize(configuration.writeKey, + config: builder.build()); + }); + + /// Resets the RudderStack client state. + Future reset() async => _execute(() => rudderClient.reset()); + + /// Disables the RudderStack client. + Future disable() async => _execute(() => rudderClient.optOut(true)); + + /// Enables the RudderStack client. + Future enable() async => _execute(() => rudderClient.optOut(false)); + + /// Sets the context for the RudderStack client. + /// + /// Takes a [token] as a parameter. + Future setContext({required String token}) async => + _execute(() => rudderClient.putDeviceToken(token)); + + /// Executes [action] and logs errors, if any. + Future _execute(Function action) async { + try { + action(); + return true; + } on Exception catch (e) { + logger.log('DerivRudderstack: $e'); + return false; + } + } +} diff --git a/packages/analytics/pubspec.yaml b/packages/analytics/pubspec.yaml index 1f61a025b..2aa153901 100644 --- a/packages/analytics/pubspec.yaml +++ b/packages/analytics/pubspec.yaml @@ -1,6 +1,6 @@ name: analytics description: A new Flutter package for collecting and sending analytical information from the app. -version: 1.0.0 +version: 1.0.1 homepage: https://deriv.com/ publish_to: "none" @@ -10,20 +10,18 @@ environment: flutter: ">=3.10.0" dependencies: - deriv_rudderstack: - git: - url: git@github.com:regentmarkets/flutter-deriv-packages.git - path: packages/deriv_rudderstack - ref: dev - - firebase_analytics: ^10.4.0 - firebase_core: ^2.12.0 + firebase_analytics: ^10.6.2 + firebase_core: ^2.20.0 + rudder_sdk_flutter: ^2.6.0 + datadog_flutter_plugin: ^1.6.2 + flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter + mocktail: ^1.0.1 flutter: diff --git a/packages/analytics/test/datadog/core/deriv_datadog_configuration_test.dart b/packages/analytics/test/datadog/core/deriv_datadog_configuration_test.dart new file mode 100644 index 000000000..eee33ff41 --- /dev/null +++ b/packages/analytics/test/datadog/core/deriv_datadog_configuration_test.dart @@ -0,0 +1,31 @@ +import 'package:analytics/sdk/datadog/core/datadog_configuration.dart'; +import 'package:analytics/sdk/datadog/core/enums.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DerivDatadogConfiguration', () { + test('DerivDatadogConfiguration', () { + const DerivDatadogConfiguration config = DerivDatadogConfiguration( + applicationId: 'myAppId', + clientToken: 'myClientToken', + env: 'development', + trackingConsent: TrackingConsent.granted, + site: DatadogSite.ap1, + nativeCrashReportEnabled: false, + sessionSamplingRate: 10, + tracingSamplingRate: 20, + serviceName: 'myServiceName', + ); + + expect(config.applicationId, 'myAppId'); + expect(config.clientToken, 'myClientToken'); + expect(config.env, 'development'); + expect(config.trackingConsent, TrackingConsent.granted); + expect(config.site, DatadogSite.ap1); + expect(config.nativeCrashReportEnabled, false); + expect(config.sessionSamplingRate, 10); + expect(config.tracingSamplingRate, 20); + expect(config.serviceName, 'myServiceName'); + }); + }); +} diff --git a/packages/analytics/test/datadog/core/enums_test.dart b/packages/analytics/test/datadog/core/enums_test.dart new file mode 100644 index 000000000..66a44497a --- /dev/null +++ b/packages/analytics/test/datadog/core/enums_test.dart @@ -0,0 +1,27 @@ +import 'package:analytics/sdk/datadog/core/enums.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart' as datadog; + +void main() { + group('Enums', () { + test( + 'TrackingConsentExtension: consent should return the corresponding datadog.TrackingConsent value', + () { + expect(TrackingConsent.granted.consent, datadog.TrackingConsent.granted); + expect(TrackingConsent.notGranted.consent, + datadog.TrackingConsent.notGranted); + expect(TrackingConsent.pending.consent, datadog.TrackingConsent.pending); + }); + + test( + 'DatadogSiteExtension: site should return the corresponding datadog.DatadogSite value', + () { + expect(DatadogSite.us1.site, datadog.DatadogSite.us1); + expect(DatadogSite.us3.site, datadog.DatadogSite.us3); + expect(DatadogSite.us5.site, datadog.DatadogSite.us5); + expect(DatadogSite.eu1.site, datadog.DatadogSite.eu1); + expect(DatadogSite.us1Fed.site, datadog.DatadogSite.us1Fed); + expect(DatadogSite.ap1.site, datadog.DatadogSite.ap1); + }); + }); +} diff --git a/packages/analytics/test/datadog/sdk/deriv_datadog_test.dart b/packages/analytics/test/datadog/sdk/deriv_datadog_test.dart new file mode 100644 index 000000000..afd8b05f1 --- /dev/null +++ b/packages/analytics/test/datadog/sdk/deriv_datadog_test.dart @@ -0,0 +1,47 @@ +import 'package:analytics/sdk/base_analytics.dart'; +import 'package:analytics/sdk/datadog/core/datadog_configuration.dart'; +import 'package:analytics/sdk/datadog/sdk/deriv_datadog.dart'; +import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart' as datadog; +import 'package:analytics/sdk/datadog/core/enums.dart' as deriv_datadog_enums; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('DerivDatadog', () { + late DerivDatadog derivDatadog; + late DatadogSdk datadogSdk; + late DerivDatadogConfiguration derivDatadogConfiguration; + + setUpAll(() { + DatadogSdk.initializeForTesting(); + datadogSdk = DatadogSdk.instance; + derivDatadog = DerivDatadog()..setDatadogSdk(datadogSdk); + derivDatadogConfiguration = const DerivDatadogConfiguration( + applicationId: 'applicationId', + clientToken: 'clientToken', + env: 'env', + trackingConsent: deriv_datadog_enums.TrackingConsent.granted, + ); + }); + + test('should return correct instance of DerivDatadog', () { + expect(derivDatadog, isA()); + expect(derivDatadog, isA>()); + }); + + test('should return correct NavigatorObserver', () { + final NavigatorObserver navigationObserver = + derivDatadog.navigatorObserver; + expect(navigationObserver, isNotNull); + expect(navigationObserver, isA()); + }); + + test('should call setup with correct parameters', () async { + final bool result = await derivDatadog.setup(derivDatadogConfiguration); + expect(result, true); + }); + }); +} diff --git a/packages/analytics/test/firebase/core/deriv_firebase_analytics_configuration_test.dart b/packages/analytics/test/firebase/core/deriv_firebase_analytics_configuration_test.dart new file mode 100644 index 000000000..1bd5a0097 --- /dev/null +++ b/packages/analytics/test/firebase/core/deriv_firebase_analytics_configuration_test.dart @@ -0,0 +1,20 @@ +import 'package:analytics/sdk/firebase/core/firebase_configuration.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('FirebaseConfiguration', () { + test('FirebaseConfiguration with isAnalyticsCollectionEnabled true', () { + const FirebaseConfiguration config = + FirebaseConfiguration(isAnalyticsCollectionEnabled: true); + + expect(config.isAnalyticsCollectionEnabled, isTrue); + }); + + test('FirebaseConfiguration with isAnalyticsCollectionEnabled false', () { + const FirebaseConfiguration config = + FirebaseConfiguration(isAnalyticsCollectionEnabled: false); + + expect(config.isAnalyticsCollectionEnabled, isFalse); + }); + }); +} diff --git a/packages/analytics/test/firebase/sdk/deriv_firebase_sdk_test.dart b/packages/analytics/test/firebase/sdk/deriv_firebase_sdk_test.dart new file mode 100644 index 000000000..7afa990fb --- /dev/null +++ b/packages/analytics/test/firebase/sdk/deriv_firebase_sdk_test.dart @@ -0,0 +1,107 @@ +import 'package:analytics/sdk/firebase/core/firebase_configuration.dart'; +import 'package:analytics/sdk/firebase/sdk/deriv_firebase_analytics.dart'; +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockFirebaseAnalytics extends Mock implements FirebaseAnalytics {} + +void main() { + late DerivFirebaseAnalytics derivFirebaseAnalytics; + late MockFirebaseAnalytics mockFirebaseAnalytics; + + setUp(() { + mockFirebaseAnalytics = MockFirebaseAnalytics(); + derivFirebaseAnalytics = DerivFirebaseAnalytics(mockFirebaseAnalytics); + }); + + group('DerivFirebaseAnalytics', () { + test('should return correct instance of DerivFirebaseAnalytics', () async { + when(() => mockFirebaseAnalytics.setAnalyticsCollectionEnabled(true)) + .thenAnswer((_) async => true); + const FirebaseConfiguration firebaseConfiguration = + FirebaseConfiguration(isAnalyticsCollectionEnabled: true); + + await derivFirebaseAnalytics.setup(firebaseConfiguration); + + verify(() => mockFirebaseAnalytics.setAnalyticsCollectionEnabled( + firebaseConfiguration.isAnalyticsCollectionEnabled)).called(1); + }); + + test('should return correct NavigatorObserver', () async { + const String screenName = 'test_screen_name'; + + when(() => mockFirebaseAnalytics.setCurrentScreen(screenName: screenName)) + .thenAnswer((_) async => true); + + await derivFirebaseAnalytics.setCurrentScreen(screenName: screenName); + + verify(() => + mockFirebaseAnalytics.setCurrentScreen(screenName: screenName)) + .called(1); + }); + + test('should call logLogin with correct parameters', () async { + const String loginMethod = 'test_login_method'; + + when(() => mockFirebaseAnalytics.logLogin(loginMethod: loginMethod)) + .thenAnswer((_) async => true); + + await derivFirebaseAnalytics.logLogin(loginMethod: loginMethod); + + verify(() => mockFirebaseAnalytics.logLogin(loginMethod: loginMethod)) + .called(1); + }); + + test('should call setUserId with correct parameters', () async { + const String userId = 'test_user_id'; + + when(() => mockFirebaseAnalytics.setUserId(id: userId)) + .thenAnswer((_) async => true); + + await derivFirebaseAnalytics.setUserId(id: userId); + + verify(() => mockFirebaseAnalytics.setUserId(id: userId)).called(1); + }); + + test('should call logEvent with correct parameters', () async { + const String name = 'test_name'; + const Map params = {}; + + when(() => mockFirebaseAnalytics.logEvent( + name: name, + parameters: params, + )).thenAnswer((_) async => true); + + await derivFirebaseAnalytics.logEvent( + name: name, + parameters: params, + ); + + verify(() => mockFirebaseAnalytics.logEvent( + name: name, + parameters: params, + )).called(1); + }); + + test('should call setUserProperty with correct parameters', () async { + const String name = 'test_name'; + const String value = 'test_value'; + + when(() => mockFirebaseAnalytics.setUserProperty( + name: name, + value: value, + )).thenAnswer((_) async => true); + + await derivFirebaseAnalytics.setUserProperty( + name: name, + value: value, + ); + + verify(() => mockFirebaseAnalytics.setUserProperty( + name: name, + value: value, + )).called(1); + }); + }); +} diff --git a/packages/analytics/test/rudderstack/core/deriv_rudderstack_configuration_test.dart b/packages/analytics/test/rudderstack/core/deriv_rudderstack_configuration_test.dart new file mode 100644 index 000000000..b0f121eb2 --- /dev/null +++ b/packages/analytics/test/rudderstack/core/deriv_rudderstack_configuration_test.dart @@ -0,0 +1,18 @@ +import 'package:analytics/sdk/rudderstack/core/rudderstack_configuration.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('RudderstackConfiguration', () { + test('RudderstackConfiguration', () { + const String dataPlaneUrl = 'https://test.dataplane.rudderstack.com'; + const String writeKey = 'test_write_key'; + const RudderstackConfiguration config = RudderstackConfiguration( + dataPlaneUrl: dataPlaneUrl, + writeKey: writeKey, + ); + + expect(config.dataPlaneUrl, dataPlaneUrl); + expect(config.writeKey, writeKey); + }); + }); +} diff --git a/packages/analytics/test/rudderstack/sdk/deriv_rudderstack_sdk_test.dart b/packages/analytics/test/rudderstack/sdk/deriv_rudderstack_sdk_test.dart new file mode 100644 index 000000000..09804da8c --- /dev/null +++ b/packages/analytics/test/rudderstack/sdk/deriv_rudderstack_sdk_test.dart @@ -0,0 +1,142 @@ +import 'package:analytics/core/logger.dart'; +import 'package:analytics/sdk/rudderstack/core/rudderstack_configuration.dart'; +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rudder_sdk_flutter/RudderController.dart'; + +class MockRudderController extends Mock implements RudderController {} + +class MockLogger extends Mock implements Logger {} + +void main() { + late DerivRudderstack derivRudderstack; + late MockRudderController mockRudderController; + late MockLogger mockLogger; + + setUp(() { + mockRudderController = MockRudderController(); + mockLogger = MockLogger(); + derivRudderstack = DerivRudderstack() + ..rudderClient = mockRudderController + ..logger = mockLogger; + }); + + group('DerivRudderstack', () { + test('identify calls rudderClient.identify', () async { + const String userId = 'test_user_id'; + + final bool result = await derivRudderstack.identify(userId: userId); + + expect(result, isTrue); + verify(() => mockRudderController.identify(userId)).called(1); + }); + + test('track calls rudderClient.track', () async { + const String eventName = 'test_event_name'; + + final bool result = await derivRudderstack.track(eventName: eventName); + + expect(result, isTrue); + verify(() => mockRudderController.track(eventName)).called(1); + }); + + test('track calls rudderClient.track', () async { + const String eventName = 'test_event_name'; + + final bool result = await derivRudderstack.track(eventName: eventName); + + expect(result, isTrue); + verify(() => mockRudderController.track(eventName)).called(1); + }); + + test('screen calls rudderClient.screen', () async { + const String screenName = 'test_screen_name'; + + final bool result = await derivRudderstack.screen(screenName: screenName); + + expect(result, isTrue); + verify(() => mockRudderController.screen(screenName)).called(1); + }); + + test('group calls rudderClient.group', () async { + const String groupId = 'test_group_id'; + + final bool result = await derivRudderstack.group(groupId: groupId); + + expect(result, isTrue); + verify(() => mockRudderController.group(groupId)).called(1); + }); + + test('alias calls rudderClient.alias', () async { + const String alias = 'test_alias'; + + final bool result = await derivRudderstack.alias(alias: alias); + + expect(result, isTrue); + verify(() => mockRudderController.alias(alias)).called(1); + }); + + test('should call initialize with correct parameters', () async { + const String dataPlaneUrl = 'https://test.dataplane.rudderstack.com'; + const String writeKey = 'test_write_key'; + + await derivRudderstack.setup(const RudderstackConfiguration( + dataPlaneUrl: dataPlaneUrl, writeKey: writeKey)); + verify(() => mockRudderController.initialize(writeKey, + config: any(named: 'config'))).called(1); + + final bool result = await derivRudderstack.setup( + const RudderstackConfiguration( + dataPlaneUrl: dataPlaneUrl, writeKey: writeKey)); + + expect(result, true); + }); + + test('should throw exception when initialize is called with wrong url', + () async { + const String dataPlaneUrl = 'wrong_url'; + const String writeKey = 'test_write_key'; + + when(() => mockRudderController.initialize(any(), config: any(named: 'config'))) + .thenThrow(Exception()); + + final bool result = await derivRudderstack.setup( + const RudderstackConfiguration( + dataPlaneUrl: dataPlaneUrl, writeKey: writeKey)); + + expect(result, false); + + }); + + test('reset calls rudderClient.reset', () async { + final bool result = await derivRudderstack.reset(); + + expect(result, isTrue); + verify(() => mockRudderController.reset()).called(1); + }); + + test('disable calls rudderClient.optOut(true)', () async { + final bool result = await derivRudderstack.disable(); + + expect(result, isTrue); + verify(() => mockRudderController.optOut(true)).called(1); + }); + + test('enable calls rudderClient.optOut(false)', () async { + final bool result = await derivRudderstack.enable(); + + expect(result, isTrue); + verify(() => mockRudderController.optOut(false)).called(1); + }); + + test('setContext calls rudderClient.putDeviceToken', () async { + const String token = 'test_token'; + + final bool result = await derivRudderstack.setContext(token: token); + + expect(result, isTrue); + verify(() => mockRudderController.putDeviceToken(token)).called(1); + }); + }); +} diff --git a/packages/deriv_auth/CHANGELOG.md b/packages/deriv_auth/CHANGELOG.md index effe43c82..65e189307 100644 --- a/packages/deriv_auth/CHANGELOG.md +++ b/packages/deriv_auth/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0 + + - **FEAT**(deriv_auth): [DERG-1299] add user agent to login ([#341](https://github.com/regentmarkets/flutter-deriv-packages/issues/341)). ([37f5b763](https://github.com/regentmarkets/flutter-deriv-packages/commit/37f5b763d8a679ff1d458fc4e9b82578f4eecf83)) + ## 1.0.0 - Initial version. diff --git a/packages/deriv_auth/lib/core/services/token/services/base_token_service.dart b/packages/deriv_auth/lib/core/services/token/services/base_token_service.dart index c9030276c..56f251c2e 100644 --- a/packages/deriv_auth/lib/core/services/token/services/base_token_service.dart +++ b/packages/deriv_auth/lib/core/services/token/services/base_token_service.dart @@ -11,5 +11,6 @@ abstract class BaseTokenService { required BaseHttpClient client, required String jwtToken, required AuthConnectionInfo connectionInfo, + String? userAgent, }); } diff --git a/packages/deriv_auth/lib/core/services/token/services/deriv_token_service.dart b/packages/deriv_auth/lib/core/services/token/services/deriv_token_service.dart index 949de9c0a..2e3028672 100644 --- a/packages/deriv_auth/lib/core/services/token/services/deriv_token_service.dart +++ b/packages/deriv_auth/lib/core/services/token/services/deriv_token_service.dart @@ -12,6 +12,7 @@ class DerivTokenService implements BaseTokenService { required BaseHttpClient client, required String jwtToken, required AuthConnectionInfo connectionInfo, + String? userAgent, }) async { /// Extract login url from connection info. final String baseUrl = 'https://${connectionInfo.endpoint}/oauth2/api/v1'; @@ -22,7 +23,10 @@ class DerivTokenService implements BaseTokenService { url: loginUrl, jsonBody: request.copyWith(appId: int.parse(connectionInfo.appId)).toJson(), - headers: {'Authorization': 'Bearer $jwtToken'}, + headers: { + 'Authorization': 'Bearer $jwtToken', + 'User-Agent': userAgent ?? 'Dart/3.0 (dart:io)' + }, ); return GetTokensResponseModel.fromJson(jsonResponse); diff --git a/packages/deriv_auth/lib/features/auth/cubit/deriv_auth_cubit.dart b/packages/deriv_auth/lib/features/auth/cubit/deriv_auth_cubit.dart index d963069c9..481787c6d 100644 --- a/packages/deriv_auth/lib/features/auth/cubit/deriv_auth_cubit.dart +++ b/packages/deriv_auth/lib/features/auth/cubit/deriv_auth_cubit.dart @@ -29,17 +29,19 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { required String email, required String password, String? otp, + String? userAgent, }) async { emit(DerivAuthLoadingState()); await _loginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: email, password: password, otp: otp, ), - false, + isSocialLogin: false, + userAgent: userAgent, ); } @@ -48,17 +50,19 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { required String oneAllConnectionToken, final String? signupProvider, String? otp, + String? userAgent, }) async { emit(DerivAuthLoadingState()); await _loginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.social, oneAllConnectionToken: oneAllConnectionToken, signupProvider: signupProvider, otp: otp, ), - true, + isSocialLogin: true, + userAgent: userAgent, ); } @@ -66,16 +70,18 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { Future socialAuth({ required SocialAuthDto socialAuthDto, String? otp, + String? userAgent, }) async { emit(DerivAuthLoadingState()); await _loginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.socialLogin, socialAuthDto: socialAuthDto, otp: otp, ), - true, + isSocialLogin: true, + userAgent: userAgent, ); } @@ -89,11 +95,16 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { ); } - Future _loginRequest( - GetTokensRequestModel request, bool isSocialLogin) async { + Future _loginRequest({ + required GetTokensRequestModel request, + required bool isSocialLogin, + String? userAgent, + }) async { try { - final AuthorizeEntity authorizeEntity = - await authService.onLoginRequest(request); + final AuthorizeEntity authorizeEntity = await authService.onLoginRequest( + request: request, + userAgent: userAgent, + ); final LandingCompanyEntity landingCompanyEntity = await authService.getLandingCompany(authorizeEntity.country); _isUserMigrated = _checkUserMigrated(authorizeEntity); @@ -182,10 +193,9 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { bool get isMigratedToWallets => _isUserMigrated; bool _checkUserMigrated(AuthorizeEntity authorizeEntity) => - authorizeEntity.accountList?.any( - (AccountListItem account) => + authorizeEntity.accountList?.any((AccountListItem account) => account.accountCategory == AccountCategoryEnum.wallet) ?? - false; + false; @override Stream get output => stream; diff --git a/packages/deriv_auth/lib/features/auth/services/base_auth_service.dart b/packages/deriv_auth/lib/features/auth/services/base_auth_service.dart index e8304cb80..f3df80e13 100644 --- a/packages/deriv_auth/lib/features/auth/services/base_auth_service.dart +++ b/packages/deriv_auth/lib/features/auth/services/base_auth_service.dart @@ -6,9 +6,10 @@ import 'package:deriv_auth/core/services/token/models/login_request.dart'; /// Interface to define all authentication-related functionality. abstract class BaseAuthService { /// Function before logging user in. - Future onLoginRequest( - GetTokensRequestModel request, - ); + Future onLoginRequest({ + required GetTokensRequestModel request, + String? userAgent, + }); /// Log in a user with [token]. Future login( diff --git a/packages/deriv_auth/lib/features/auth/services/deriv_auth_service.dart b/packages/deriv_auth/lib/features/auth/services/deriv_auth_service.dart index 3aabac973..6c2db5a5d 100644 --- a/packages/deriv_auth/lib/features/auth/services/deriv_auth_service.dart +++ b/packages/deriv_auth/lib/features/auth/services/deriv_auth_service.dart @@ -40,10 +40,11 @@ class DerivAuthService extends BaseAuthService { final BaseTokenService tokenService; @override - Future onLoginRequest( - GetTokensRequestModel request, [ + Future onLoginRequest({ + required GetTokensRequestModel request, + String? userAgent, Function? onInvalidJwtToken, - ]) async { + }) async { try { final String jwtToken = await jwtService.getJwtToken(); @@ -52,6 +53,7 @@ class DerivAuthService extends BaseAuthService { client: HttpClient(), jwtToken: jwtToken, connectionInfo: connectionInfo, + userAgent: userAgent, ); final List _supportedAccounts = @@ -78,7 +80,7 @@ class DerivAuthService extends BaseAuthService { jwtService.clearJwtToken(); - return onLoginRequest(request); + return onLoginRequest(request: request, userAgent: userAgent); } else { throw _mapHttpErrorToDerivAuthError(error); } @@ -200,7 +202,7 @@ class DerivAuthService extends BaseAuthService { return DerivAuthException( type: AuthErrorType.invalidResidence, message: exception.message, - ); + ); default: return DerivAuthException( diff --git a/packages/deriv_auth/pubspec.yaml b/packages/deriv_auth/pubspec.yaml index 764862ae5..8c8417d88 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: 1.0.2 +version: 1.1.0 publish_to: "none" @@ -12,7 +12,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_http_client - ref: dev + ref: deriv_http_client-v1.0.0 bloc: ^8.1.1 crypto: ^3.0.2 diff --git a/packages/deriv_auth/test/features/auth/cubit/deriv_auth_cubit_test.dart b/packages/deriv_auth/test/features/auth/cubit/deriv_auth_cubit_test.dart index 0c3254c1b..73829497f 100644 --- a/packages/deriv_auth/test/features/auth/cubit/deriv_auth_cubit_test.dart +++ b/packages/deriv_auth/test/features/auth/cubit/deriv_auth_cubit_test.dart @@ -90,8 +90,9 @@ void main() { () { registerFallbackValue(GetTokensRequestModel(type: AuthType.system)); - when(() => service.onLoginRequest(any())).thenAnswer( - (_) => Future.value(mockedValidAuthorizeEntity)); + when(() => service.onLoginRequest(request: any(named: 'request'))) + .thenAnswer((_) => + Future.value(mockedValidAuthorizeEntity)); final List> expectedResponse = >[ @@ -106,7 +107,8 @@ void main() { authCubit.systemLogin(email: 'email', password: 'password'); - verify(() => service.onLoginRequest(any())).called(1); + verify(() => service.onLoginRequest(request: any(named: 'request'))) + .called(1); }); test( @@ -114,8 +116,9 @@ void main() { () { registerFallbackValue(GetTokensRequestModel(type: AuthType.social)); - when(() => service.onLoginRequest(any())).thenAnswer( - (_) => Future.value(mockedValidAuthorizeEntity)); + when(() => service.onLoginRequest(request: any(named: 'request'))) + .thenAnswer((_) => + Future.value(mockedValidAuthorizeEntity)); final List> expectedResponse = >[ @@ -130,7 +133,8 @@ void main() { authCubit.socialLogin(oneAllConnectionToken: 'token'); - verify(() => service.onLoginRequest(any())).called(1); + verify(() => service.onLoginRequest(request: any(named: 'request'))) + .called(1); }); test( @@ -138,8 +142,9 @@ void main() { () { registerFallbackValue(GetTokensRequestModel(type: AuthType.social)); - when(() => service.onLoginRequest(any())).thenAnswer( - (_) => Future.value(mockedValidAuthorizeEntity)); + when(() => service.onLoginRequest(request: any(named: 'request'))) + .thenAnswer((_) => + Future.value(mockedValidAuthorizeEntity)); final List> expectedResponse = >[ @@ -156,12 +161,14 @@ void main() { socialAuthDto: mockSocialAuthDto, ); - verify(() => service.onLoginRequest(any())).called(1); + verify(() => service.onLoginRequest(request: any(named: 'request'))) + .called(1); }); test('should emit [AuthLoggedInState] upon a successful otp login.', () { - when(() => service.onLoginRequest(any())).thenAnswer( - (_) => Future.value(mockedValidAuthorizeEntity)); + when(() => service.onLoginRequest(request: any(named: 'request'))) + .thenAnswer((_) => + Future.value(mockedValidAuthorizeEntity)); final List> expectedResponse = >[ @@ -175,14 +182,16 @@ void main() { ); authCubit.systemLogin(email: 'email', password: 'pass', otp: 'otp'); - verify(() => service.onLoginRequest(any())).called(1); + verify(() => service.onLoginRequest(request: any(named: 'request'))) + .called(1); }); test('should emit [AuthErrorState] when an exception occurs in service.', () { registerFallbackValue(GetTokensRequestModel(type: AuthType.system)); - when(() => service.onLoginRequest(any())).thenThrow(DerivAuthException( + when(() => service.onLoginRequest(request: any(named: 'request'))) + .thenThrow(DerivAuthException( message: 'message', type: AuthErrorType.expiredAccount, )); @@ -200,7 +209,8 @@ void main() { authCubit.systemLogin(email: 'email', password: 'pass'); - verify(() => service.onLoginRequest(any())).called(1); + verify(() => service.onLoginRequest(request: any(named: 'request'))) + .called(1); }); test('should emit [AuthLoggedOutState] upon a successful logout.', () { when(() => service.logout()).thenAnswer((_) => Future.value()); diff --git a/packages/deriv_auth/test/features/auth/service/deriv_auth_service_test.dart b/packages/deriv_auth/test/features/auth/service/deriv_auth_service_test.dart index 3e77e295a..43541c07d 100644 --- a/packages/deriv_auth/test/features/auth/service/deriv_auth_service_test.dart +++ b/packages/deriv_auth/test/features/auth/service/deriv_auth_service_test.dart @@ -127,7 +127,7 @@ void main() { ); final AuthorizeEntity response = await authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -154,7 +154,7 @@ void main() { ); final AuthorizeEntity response = await authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.social, signupProvider: 'signupProvider', oneAllConnectionToken: 'oneAllConnectionToken', @@ -180,7 +180,7 @@ void main() { ); final AuthorizeEntity response = await authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.socialLogin, signupProvider: 'signupProvider', socialAuthDto: mockSocialAuthDto, @@ -207,16 +207,17 @@ void main() { ); final AuthorizeEntity response = await authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', signupProvider: 'signupProvider', - ), () { - when(() => jwtService.getJwtToken()).thenAnswer( - (_) => Future.value(validJwtToken), - ); - }); + ), + onInvalidJwtToken: () { + when(() => jwtService.getJwtToken()).thenAnswer( + (_) => Future.value(validJwtToken), + ); + }); verify(() => jwtService.getJwtToken()).called(2); @@ -251,7 +252,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -288,7 +289,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -325,7 +326,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -434,7 +435,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -471,7 +472,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -508,7 +509,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', diff --git a/packages/deriv_auth_ui/CHANGELOG.md b/packages/deriv_auth_ui/CHANGELOG.md index 5a8e15d2f..9a9cea2da 100644 --- a/packages/deriv_auth_ui/CHANGELOG.md +++ b/packages/deriv_auth_ui/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.0.1+3 + + - Update a dependency to the latest release. + +## 0.0.1+2 + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + +## 0.0.1+1 + + - Update a dependency to the latest release. + ## 0.0.1 * Init project diff --git a/packages/deriv_auth_ui/README.md b/packages/deriv_auth_ui/README.md index 504ab5166..c98babcc0 100644 --- a/packages/deriv_auth_ui/README.md +++ b/packages/deriv_auth_ui/README.md @@ -47,6 +47,63 @@ MaterialApp( ) ``` +## AuthErrorHandler + +Since `DerivAuthCubit` influences many features like - login, signup, change password, etc. To be ease the error handling by making it needed to implement in only one place and to make sure all the auth error cases has been handled we have created a base class which client app can extend if they want changes in the default error handling. + +```dart +base class AuthErrorStateHandler { + /// {@macro default_auth_error_state_handler} + AuthErrorStateHandler({ + required this.context, + }); + + /// The [BuildContext] of the widget that is using this handler. + final BuildContext context; + + /// On invalid 2FA code. + void invalid2faCode(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informInvalid2FACode, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// On invalid email or password. + void invalidEmailOrPassword(DerivAuthErrorState state) { + //.... + } + + // ... +} +``` +If client app wants to customize the error handling they can extend the `AuthErrorStateHandler` and override the methods they want to customize. + +```dart + +final class CustomAuthErrorStateHandler extends AuthErrorStateHandler { + CustomAuthErrorStateHandler({ + required BuildContext context, + }) : super(context: context); + + @override + void invalid2faCode(DerivAuthErrorState state) { + //... + } +} +``` +The client app can pass the custom error handler to the layout's constructor. + +```dart +DerivLoginLayout( + // ... + authErrorStateHandler: CustomAuthErrorStateHandler(context: context), + // ... +) +``` +The package handles the mapping of the error state to the corresponding method in the `AuthErrorStateHandler` class within the layout. + ## Layouts provided: ### - Get Started Flow @@ -60,6 +117,9 @@ MaterialApp( backgroundImagePath: backgroundImagePath, onLoginTapped: () {}, onSignupTapped: () {}, + onTapNavigation: (){ + // Callback to be called when pressed on the screen seven times + }, ); ``` ### - Login Flow @@ -69,6 +129,7 @@ MaterialApp( welcomeLabel: 'Welcome back!', greetingLabel: 'Log in to your Deriv account to start trading and investing.', + authErrorStateHandler: AuthErrorStateHandler(context: context), onResetPassTapped: () { // Navigate to reset password page }, @@ -99,6 +160,7 @@ MaterialApp( ``` dart DerivSignupLayout( signupPageLabel: 'Start trading with Deriv', + authErrorStateHandler: AuthErrorStateHandler(context: context), signupPageDescription: 'Join over 1 million traders worldwide who loves trading at Deriv.', onSocialAuthButtonPressed: (SocialAuthProvider provider) {}, @@ -140,6 +202,7 @@ MaterialApp( ``` dart DerivSetPasswordLayout( onDerivAuthState: (BuildContext, DerivAuthState) {}, + authErrorStateHandler: AuthErrorStateHandler(context: context), onDerivSignupState: (BuildContext, DerivSignupState) {}, onPreviousPressed: () {}, verificationCode: '123456', @@ -164,3 +227,72 @@ MaterialApp( ), ``` +- **Reset Password Success Layout** + ``` dart + DerivSuccessPassChangeLayout(); + ``` + + +## Additional: + +### AuthListener + +`AuthListener` is a widget that listens to the `DerivAuthCubit` state and calls the corresponding callback. This widget is created for ease of using `AuthErrorStateHandler` by handling the mapping of the error state to the corresponding method in the `AuthErrorStateHandler` class. + + +```dart +class DerivAuthStateListener extends StatelessWidget { + /// {@macro auth_state_listener} + const DerivAuthStateListener({ + required this.child, + super.key, + this.onLoggedIn, + this.onLoggedOut, + this.onLoading, + this.onError, + this.authErrorStateHandler, + }); + + /// The [Widget] that is using this [DerivAuthStateListener]. + final Widget child; + + /// Callback to be called when user is logged in. + final Function(DerivAuthLoggedInState)? onLoggedIn; + + /// Callback to be called when user is logged out. + final VoidCallback? onLoggedOut; + + /// Callback to be called when user is logging in. + final VoidCallback? onLoading; + + /// Callback to be called when an error occurs. + final Function(DerivAuthErrorState)? onError; + + /// Extension of base [AuthErrorStateHandler]. If not provided, base implementation will be used. + final AuthErrorStateHandler? authErrorStateHandler; + + @override + Widget build(BuildContext context) => + BlocListener( + listener: (BuildContext context, DerivAuthState state) { + if (state is DerivAuthLoggedInState) { + onLoggedIn?.call(state); + } else if (state is DerivAuthLoggedOutState) { + onLoggedOut?.call(); + } else if (state is DerivAuthLoadingState) { + onLoading?.call(); + } else if (state is DerivAuthErrorState) { + onError?.call(state); + + authErrorStateMapper( + authErrorState: state, + authErrorStateHandler: authErrorStateHandler ?? + AuthErrorStateHandler(context: context), + ); + } + }, + child: child, + ); +} + +``` \ No newline at end of file diff --git a/packages/deriv_auth_ui/analysis_options.yaml b/packages/deriv_auth_ui/analysis_options.yaml index 3d0c05ee4..2681030b5 100644 --- a/packages/deriv_auth_ui/analysis_options.yaml +++ b/packages/deriv_auth_ui/analysis_options.yaml @@ -58,12 +58,10 @@ linter: - flutter_style_todos - hash_and_equals - implementation_imports - - iterable_contains_unrelated_type + - collection_methods_unrelated_type - join_return_with_assignment - library_names - library_prefixes - # - lines_longer_than_80_chars - - list_remove_unrelated_type - no_adjacent_strings_in_list - no_duplicate_case_values - no_leading_underscores_for_local_identifiers diff --git a/packages/deriv_auth_ui/example/lib/core/example_auth_error_state_handler.dart b/packages/deriv_auth_ui/example/lib/core/example_auth_error_state_handler.dart new file mode 100644 index 000000000..478000d8e --- /dev/null +++ b/packages/deriv_auth_ui/example/lib/core/example_auth_error_state_handler.dart @@ -0,0 +1,16 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:flutter/material.dart'; + +final class ExampleAuthErrorStateHandler extends AuthErrorStateHandler { + ExampleAuthErrorStateHandler({required super.context}); + + @override + void onInvalidCredentials(DerivAuthErrorState state) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.message), + ), + ); + } +} diff --git a/packages/deriv_auth_ui/example/lib/features/get_started/pages/get_started_page.dart b/packages/deriv_auth_ui/example/lib/features/get_started/pages/get_started_page.dart index d4ea64fe0..a89b5e67f 100644 --- a/packages/deriv_auth_ui/example/lib/features/get_started/pages/get_started_page.dart +++ b/packages/deriv_auth_ui/example/lib/features/get_started/pages/get_started_page.dart @@ -30,5 +30,6 @@ class GetStartedPage extends StatelessWidget { builder: (context) => const SignupPage(), ), ), + onTapNavigation: () {}, ); } diff --git a/packages/deriv_auth_ui/example/lib/features/login/pages/login_page.dart b/packages/deriv_auth_ui/example/lib/features/login/pages/login_page.dart index 7d1e66a2a..2c6ad3518 100644 --- a/packages/deriv_auth_ui/example/lib/features/login/pages/login_page.dart +++ b/packages/deriv_auth_ui/example/lib/features/login/pages/login_page.dart @@ -1,5 +1,6 @@ import 'package:deriv_auth/features/auth/cubit/deriv_auth_cubit.dart'; import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:example/core/example_auth_error_state_handler.dart'; import 'package:example/features/home/pages/home_page.dart'; import 'package:example/features/reset_pass/pages/reset_pass_page.dart'; import 'package:example/features/signup/pages/signup_page.dart'; @@ -32,6 +33,7 @@ class _LoginPageState extends State { builder: (context) => const HomePage(), ), ), + authErrorStateHandler: ExampleAuthErrorStateHandler(context: context), onLoginError: (_) {}, onResetPassTapped: () => Navigator.push( context, diff --git a/packages/deriv_auth_ui/example/lib/features/reset_pass/pages/choose_new_password_page.dart b/packages/deriv_auth_ui/example/lib/features/reset_pass/pages/choose_new_password_page.dart index 6ce9f1198..49dbbd6c9 100644 --- a/packages/deriv_auth_ui/example/lib/features/reset_pass/pages/choose_new_password_page.dart +++ b/packages/deriv_auth_ui/example/lib/features/reset_pass/pages/choose_new_password_page.dart @@ -1,6 +1,6 @@ import 'package:deriv_auth/features/reset_password/cubit/reset_password_cubit.dart'; import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:example/features/get_started/pages/get_started_page.dart'; +import 'package:example/features/reset_pass/pages/reset_pass_success_page.dart'; import 'package:flutter/material.dart'; class ChooseNewPasswordPage extends StatelessWidget { @@ -13,11 +13,10 @@ class ChooseNewPasswordPage extends StatelessWidget { return DerivChooseNewPassLayout( token: 'token', onResetPassSucceed: () { - Navigator.of(context).pushAndRemoveUntil( + Navigator.of(context).push( MaterialPageRoute( - builder: (context) => const GetStartedPage(), + builder: (context) => const ResetPassSuccessPage(), ), - (Route route) => false, ); }, onResetPassError: (_) {}, diff --git a/packages/deriv_auth_ui/example/lib/features/reset_pass/pages/reset_pass_success_page.dart b/packages/deriv_auth_ui/example/lib/features/reset_pass/pages/reset_pass_success_page.dart new file mode 100644 index 000000000..a0de6729d --- /dev/null +++ b/packages/deriv_auth_ui/example/lib/features/reset_pass/pages/reset_pass_success_page.dart @@ -0,0 +1,51 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:example/features/get_started/pages/get_started_page.dart'; +import 'package:example/features/login/pages/login_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ResetPassSuccessPage extends StatefulWidget { + const ResetPassSuccessPage({super.key}); + + @override + State createState() => _ResetPassSuccessPageState(); +} + +class _ResetPassSuccessPageState extends State { + static const Duration _successPageHoldDuration = Duration(seconds: 2); + + @override + void initState() { + super.initState(); + + // wait for either [_successPageHoldDuration] or logout to finish + // then navigate to loginPage + Future.wait( + >[ + Future.delayed(_successPageHoldDuration), + BlocProvider.of(context).logout(), + ], + ).then( + (_) { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => const GetStartedPage(), + ), + (route) => false, + ); + + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const LoginPage(), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return const DerivSuccessPassChangeLayout(); + } +} diff --git a/packages/deriv_auth_ui/example/lib/features/signup/pages/set_password_page.dart b/packages/deriv_auth_ui/example/lib/features/signup/pages/set_password_page.dart index e009e632e..32b6b7cfa 100644 --- a/packages/deriv_auth_ui/example/lib/features/signup/pages/set_password_page.dart +++ b/packages/deriv_auth_ui/example/lib/features/signup/pages/set_password_page.dart @@ -1,6 +1,6 @@ import 'package:deriv_auth/deriv_auth.dart'; import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:example/features/home/pages/home_page.dart'; +import 'package:example/core/example_auth_error_state_handler.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -12,16 +12,7 @@ class SetPasswordPage extends StatelessWidget { @override Widget build(BuildContext context) { return DerivSetPasswordLayout( - onDerivAuthState: (context, state) { - if (state is DerivAuthLoggedInState) { - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (context) => const HomePage(), - ), - (Route route) => false, - ); - } - }, + authErrorStateHandler: ExampleAuthErrorStateHandler(context: context), onDerivSignupState: (context, state) { if (state is DerivSignupDoneState) { context diff --git a/packages/deriv_auth_ui/example/lib/features/signup/pages/signup_page.dart b/packages/deriv_auth_ui/example/lib/features/signup/pages/signup_page.dart index c4667ba7f..c275b0dc7 100644 --- a/packages/deriv_auth_ui/example/lib/features/signup/pages/signup_page.dart +++ b/packages/deriv_auth_ui/example/lib/features/signup/pages/signup_page.dart @@ -1,4 +1,5 @@ import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:example/core/example_auth_error_state_handler.dart'; import 'package:example/features/login/pages/login_page.dart'; import 'package:example/features/signup/pages/verify_email_page.dart'; import 'package:flutter/material.dart'; @@ -13,6 +14,7 @@ class SignupPage extends StatefulWidget { class _SignupPageState extends State { @override Widget build(BuildContext context) => DerivSignupLayout( + authErrorStateHandler: ExampleAuthErrorStateHandler(context: context), signupPageLabel: 'Start trading with Deriv', signupPageDescription: 'Join over 1 million traders worldwide who loves trading at Deriv.', diff --git a/packages/deriv_auth_ui/example/pubspec.yaml b/packages/deriv_auth_ui/example/pubspec.yaml index d72faeb0d..44b8282a0 100644 --- a/packages/deriv_auth_ui/example/pubspec.yaml +++ b/packages/deriv_auth_ui/example/pubspec.yaml @@ -14,17 +14,6 @@ dependencies: cupertino_icons: ^1.0.2 deriv_auth_ui: path: ../ - deriv_auth: - git: - url: git@github.com:regentmarkets/flutter-deriv-packages.git - path: packages/deriv_auth - ref: d5bef178341a318f911b42b8105d8c3d50c6b63e - deriv_theme: - path: ../../deriv_theme - # git: - # url: git@github.com:regentmarkets/flutter-deriv-packages.git - # path: packages/deriv_theme - # ref: dev flutter_bloc: ^8.1.3 http: ^0.13.6 diff --git a/packages/deriv_auth_ui/lib/deriv_auth_ui.dart b/packages/deriv_auth_ui/lib/deriv_auth_ui.dart index c03e33998..fc72bd304 100644 --- a/packages/deriv_auth_ui/lib/deriv_auth_ui.dart +++ b/packages/deriv_auth_ui/lib/deriv_auth_ui.dart @@ -5,6 +5,8 @@ export 'src/features/login/layouts/deriv_2fa_layout.dart'; export 'src/features/login/layouts/deriv_login_layout.dart'; export 'src/features/reset_pass/layouts/deriv_choose_new_pass_layout.dart'; export 'src/features/reset_pass/layouts/deriv_reset_pass_layout.dart'; +export 'src/features/reset_pass/layouts/deriv_success_pass_change_layout.dart'; +export 'src/features/setting_page/layouts/deriv_setting_layout.dart'; export 'src/features/signup/cubits/deriv_country_selection_cubit.dart'; export 'src/features/signup/layouts/deriv_country_selection_layout.dart'; export 'src/features/signup/layouts/deriv_email_not_received_layout.dart'; @@ -15,3 +17,4 @@ export 'src/features/signup/layouts/deriv_verify_email_layout.dart'; export 'src/features/signup/models/deriv_auth_utm_model.dart'; export 'src/features/signup/models/deriv_password_policy_model.dart'; export 'src/features/signup/models/deriv_residence_model.dart'; +export 'src/core/states/states.dart'; diff --git a/packages/deriv_auth_ui/lib/generated/intl/messages_en.dart b/packages/deriv_auth_ui/lib/generated/intl/messages_en.dart index 927d0cfc3..35d116dda 100644 --- a/packages/deriv_auth_ui/lib/generated/intl/messages_en.dart +++ b/packages/deriv_auth_ui/lib/generated/intl/messages_en.dart @@ -43,6 +43,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Get a free account"), "actionLogin": MessageLookupByLibrary.simpleMessage("Log in"), "actionNext": MessageLookupByLibrary.simpleMessage("Next"), + "actionOk": MessageLookupByLibrary.simpleMessage("OK"), "actionPrevious": MessageLookupByLibrary.simpleMessage("Previous"), "actionProceed": MessageLookupByLibrary.simpleMessage("Proceed"), "actionReenterEmail": MessageLookupByLibrary.simpleMessage( @@ -51,20 +52,39 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Reset my password"), "actionStartTrading": MessageLookupByLibrary.simpleMessage("Start trading"), + "actionTryAgain": MessageLookupByLibrary.simpleMessage("Try Again"), "infoReferralInfoDescription": MessageLookupByLibrary.simpleMessage( "An alphanumeric code provided by a Deriv affiliate, applicable for email sign-ups only."), + "informConnectionError": MessageLookupByLibrary.simpleMessage( + "Connection error. Please try again later."), + "informDeactivatedAccount": MessageLookupByLibrary.simpleMessage( + "Your account is deactivated."), "informEnterTwoFactorAuthCode": MessageLookupByLibrary.simpleMessage( "Enter the 6-digit code from the authenticator app on your phone."), + "informExpiredAccount": + MessageLookupByLibrary.simpleMessage("Your account is expired"), + "informFailedAuthentication": MessageLookupByLibrary.simpleMessage( + "Your email or password may be incorrect. Did you sign up with a social account? Check and try again."), + "informFailedAuthorization": + MessageLookupByLibrary.simpleMessage("Authorization failed."), + "informInvalid2FACode": MessageLookupByLibrary.simpleMessage( + "The code you entered is invalid. Check and try again."), + "informInvalidCredentials": + MessageLookupByLibrary.simpleMessage("Invalid credentials."), "informInvalidEmailFormat": MessageLookupByLibrary.simpleMessage("Enter a valid email address"), "informInvalidPasswordFormat": MessageLookupByLibrary.simpleMessage( "Please enter a valid password format"), "informInvalidReferralCode": MessageLookupByLibrary.simpleMessage( "The referral code you entered is invalid. Check and try again."), + "informInvalidResidence": + MessageLookupByLibrary.simpleMessage("Invalid residence."), "informLetsContinue": MessageLookupByLibrary.simpleMessage("Let\'s continue."), "informLoginOptions": MessageLookupByLibrary.simpleMessage("Or log in with"), + "informMissingOtp": + MessageLookupByLibrary.simpleMessage("Missing one-time password."), "informPasswordPolicy": MessageLookupByLibrary.simpleMessage("Your password must have:"), "informPasswordPolicyLength": @@ -78,20 +98,33 @@ class MessageLookup extends MessageLookupByLibrary { "You’ll need to log in with your new password. Hang on, we’re redirecting you."), "informResetPassByEmail": MessageLookupByLibrary.simpleMessage( "We\'ll email you instructions to reset your password."), + "informSelfClosed": MessageLookupByLibrary.simpleMessage( + "Your account has been closed."), "informSendResetPasswordEmail": m0, + "informSwitchAccountError": MessageLookupByLibrary.simpleMessage( + "Switch account error. Please try again later."), + "informUnexpectedError": MessageLookupByLibrary.simpleMessage( + "An unexpected error occurred."), + "informUnsupportedCountry": MessageLookupByLibrary.simpleMessage( + "Your country is not supported."), "informVerificationEmailSent": m1, "informYourPassHasBeenReset": MessageLookupByLibrary.simpleMessage( "Your password has been reset"), + "labelApplicationID": + MessageLookupByLibrary.simpleMessage("Application ID"), "labelCheckEmail": MessageLookupByLibrary.simpleMessage("Check your email"), "labelChooseCountry": MessageLookupByLibrary.simpleMessage("Choose country"), "labelChooseNewPass": MessageLookupByLibrary.simpleMessage("Choose a new password"), + "labelCountryConsentBrazil": MessageLookupByLibrary.simpleMessage( + "I hereby confirm that my request for opening an account with Deriv to trade OTC products issued and offered exclusively outside Brazil was initiated by me. I fully understand that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a relation with a foreign company."), "labelCreatePass": MessageLookupByLibrary.simpleMessage("Create a password"), "labelCreatePassword": MessageLookupByLibrary.simpleMessage("Create a password"), + "labelDeveloper": MessageLookupByLibrary.simpleMessage("Developer"), "labelDontHaveAnAccountYet": MessageLookupByLibrary.simpleMessage("Don’t have an account yet?"), "labelEmail": MessageLookupByLibrary.simpleMessage("Email"), @@ -105,6 +138,7 @@ class MessageLookup extends MessageLookupByLibrary { "The email address you entered had a mistake or typo (happens to the best of us)."), "labelEmailIssueWrongEmail": MessageLookupByLibrary.simpleMessage( "You accidentally gave us another email address (Usually a work or a personal one instead of the one you meant)."), + "labelEndpoint": MessageLookupByLibrary.simpleMessage("Endpoint"), "labelGotReferralCode": MessageLookupByLibrary.simpleMessage("Got a referral code?"), "labelHaveAccount": @@ -137,8 +171,15 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("2FA code"), "labelVerifyYourEmail": MessageLookupByLibrary.simpleMessage("Verify your email"), + "semanticApplicationID": + MessageLookupByLibrary.simpleMessage("Application ID"), + "semanticEndpoint": MessageLookupByLibrary.simpleMessage("Endpoint"), "warnCountryNotAvailable": MessageLookupByLibrary.simpleMessage( "Unfortunately, Deriv is not available in your country."), + "warnInvalidApplicationID": + MessageLookupByLibrary.simpleMessage("invalid application ID"), + "warnInvalidEndpoint": + MessageLookupByLibrary.simpleMessage("invalid endpoint"), "warnNotAvailableCountries": MessageLookupByLibrary.simpleMessage( "If you have any questions, contact us via "), "warnNotAvailableCountriesTitle": m2, diff --git a/packages/deriv_auth_ui/lib/generated/l10n.dart b/packages/deriv_auth_ui/lib/generated/l10n.dart index 7d4ef2308..55d19fff0 100644 --- a/packages/deriv_auth_ui/lib/generated/l10n.dart +++ b/packages/deriv_auth_ui/lib/generated/l10n.dart @@ -71,6 +71,16 @@ class DerivAuthUILocalization { ); } + /// `OK` + String get actionOk { + return Intl.message( + 'OK', + name: 'actionOk', + desc: '', + args: [], + ); + } + /// `If you have any questions, contact us via ` String get warnNotAvailableCountries { return Intl.message( @@ -690,6 +700,226 @@ class DerivAuthUILocalization { args: [], ); } + + /// `Try Again` + String get actionTryAgain { + return Intl.message( + 'Try Again', + name: 'actionTryAgain', + desc: '', + args: [], + ); + } + + /// `The code you entered is invalid. Check and try again.` + String get informInvalid2FACode { + return Intl.message( + 'The code you entered is invalid. Check and try again.', + name: 'informInvalid2FACode', + desc: '', + args: [], + ); + } + + /// `Your email or password may be incorrect. Did you sign up with a social account? Check and try again.` + String get informFailedAuthentication { + return Intl.message( + 'Your email or password may be incorrect. Did you sign up with a social account? Check and try again.', + name: 'informFailedAuthentication', + desc: '', + args: [], + ); + } + + /// `Your account is deactivated.` + String get informDeactivatedAccount { + return Intl.message( + 'Your account is deactivated.', + name: 'informDeactivatedAccount', + desc: '', + args: [], + ); + } + + /// `Authorization failed.` + String get informFailedAuthorization { + return Intl.message( + 'Authorization failed.', + name: 'informFailedAuthorization', + desc: '', + args: [], + ); + } + + /// `Invalid residence.` + String get informInvalidResidence { + return Intl.message( + 'Invalid residence.', + name: 'informInvalidResidence', + desc: '', + args: [], + ); + } + + /// `Invalid credentials.` + String get informInvalidCredentials { + return Intl.message( + 'Invalid credentials.', + name: 'informInvalidCredentials', + desc: '', + args: [], + ); + } + + /// `Missing one-time password.` + String get informMissingOtp { + return Intl.message( + 'Missing one-time password.', + name: 'informMissingOtp', + desc: '', + args: [], + ); + } + + /// `Your account has been closed.` + String get informSelfClosed { + return Intl.message( + 'Your account has been closed.', + name: 'informSelfClosed', + desc: '', + args: [], + ); + } + + /// `An unexpected error occurred.` + String get informUnexpectedError { + return Intl.message( + 'An unexpected error occurred.', + name: 'informUnexpectedError', + desc: '', + args: [], + ); + } + + /// `Your country is not supported.` + String get informUnsupportedCountry { + return Intl.message( + 'Your country is not supported.', + name: 'informUnsupportedCountry', + desc: '', + args: [], + ); + } + + /// `Your account is expired` + String get informExpiredAccount { + return Intl.message( + 'Your account is expired', + name: 'informExpiredAccount', + desc: '', + args: [], + ); + } + + /// `I hereby confirm that my request for opening an account with Deriv to trade OTC products issued and offered exclusively outside Brazil was initiated by me. I fully understand that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a relation with a foreign company.` + String get labelCountryConsentBrazil { + return Intl.message( + 'I hereby confirm that my request for opening an account with Deriv to trade OTC products issued and offered exclusively outside Brazil was initiated by me. I fully understand that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a relation with a foreign company.', + name: 'labelCountryConsentBrazil', + desc: '', + args: [], + ); + } + + /// `Connection error. Please try again later.` + String get informConnectionError { + return Intl.message( + 'Connection error. Please try again later.', + name: 'informConnectionError', + desc: '', + args: [], + ); + } + + /// `Switch account error. Please try again later.` + String get informSwitchAccountError { + return Intl.message( + 'Switch account error. Please try again later.', + name: 'informSwitchAccountError', + desc: '', + args: [], + ); + } + + /// `Developer` + String get labelDeveloper { + return Intl.message( + 'Developer', + name: 'labelDeveloper', + desc: '', + args: [], + ); + } + + /// `Endpoint` + String get labelEndpoint { + return Intl.message( + 'Endpoint', + name: 'labelEndpoint', + desc: '', + args: [], + ); + } + + /// `Endpoint` + String get semanticEndpoint { + return Intl.message( + 'Endpoint', + name: 'semanticEndpoint', + desc: '', + args: [], + ); + } + + /// `invalid endpoint` + String get warnInvalidEndpoint { + return Intl.message( + 'invalid endpoint', + name: 'warnInvalidEndpoint', + desc: '', + args: [], + ); + } + + /// `Application ID` + String get labelApplicationID { + return Intl.message( + 'Application ID', + name: 'labelApplicationID', + desc: '', + args: [], + ); + } + + /// `Application ID` + String get semanticApplicationID { + return Intl.message( + 'Application ID', + name: 'semanticApplicationID', + desc: '', + args: [], + ); + } + + /// `invalid application ID` + String get warnInvalidApplicationID { + return Intl.message( + 'invalid application ID', + name: 'warnInvalidApplicationID', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate diff --git a/packages/deriv_auth_ui/lib/l10n/intl_en.arb b/packages/deriv_auth_ui/lib/l10n/intl_en.arb index 337c4193e..fad40dd85 100644 --- a/packages/deriv_auth_ui/lib/l10n/intl_en.arb +++ b/packages/deriv_auth_ui/lib/l10n/intl_en.arb @@ -10,6 +10,7 @@ } } }, + "actionOk": "OK", "warnNotAvailableCountries": "If you have any questions, contact us via ", "labelLiveChat": "Live chat", "actionGetAFreeAccount": "Get a free account", @@ -89,5 +90,27 @@ "informPasswordPolicyLowerAndUpper": "Upper and lower case letters", "informPasswordPolicyNumber": "At least one number", "warnPasswordContainsSymbol": "Use symbols for strong password.", - "labelReferralCode": "Referral Code" -} \ No newline at end of file + "labelReferralCode": "Referral Code", + "actionTryAgain": "Try Again", + "informInvalid2FACode": "The code you entered is invalid. Check and try again.", + "informFailedAuthentication": "Your email or password may be incorrect. Did you sign up with a social account? Check and try again.", + "informDeactivatedAccount": "Your account is deactivated.", + "informFailedAuthorization": "Authorization failed.", + "informInvalidResidence": "Invalid residence.", + "informInvalidCredentials": "Invalid credentials.", + "informMissingOtp": "Missing one-time password.", + "informSelfClosed": "Your account has been closed.", + "informUnexpectedError": "An unexpected error occurred.", + "informUnsupportedCountry": "Your country is not supported.", + "informExpiredAccount": "Your account is expired", + "labelCountryConsentBrazil": "I hereby confirm that my request for opening an account with Deriv to trade OTC products issued and offered exclusively outside Brazil was initiated by me. I fully understand that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a relation with a foreign company.", + "informConnectionError": "Connection error. Please try again later.", + "informSwitchAccountError": "Switch account error. Please try again later.", + "labelDeveloper": "Developer", + "labelEndpoint": "Endpoint", + "semanticEndpoint": "Endpoint", + "warnInvalidEndpoint": "invalid endpoint", + "labelApplicationID": "Application ID", + "semanticApplicationID": "Application ID", + "warnInvalidApplicationID": "invalid application ID" +} diff --git a/packages/deriv_auth_ui/lib/src/core/helpers/country_selection_helper.dart b/packages/deriv_auth_ui/lib/src/core/helpers/country_selection_helper.dart new file mode 100644 index 000000000..5af80cb6a --- /dev/null +++ b/packages/deriv_auth_ui/lib/src/core/helpers/country_selection_helper.dart @@ -0,0 +1,86 @@ +import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; +import 'package:flutter/material.dart'; + +/// Returns `true` if the country is not in the [notAllowedCountryCodes] set, +/// otherwise returns `false`. +bool isAllowedCountry({required String? countryCode}) => + !notAllowedCountryCodes.contains(countryCode); + +/// Returns `true` if consent is required for the given [countryCode], +/// otherwise returns `false`. +bool isConsentRequired({required String? countryCode}) { + if (countryCode == null) { + return false; + } + + return countriesRequiringConsent.contains(countryCode.toLowerCase()); +} + +/// Retrieves the consent message for a given country code. +/// +/// If the country requires consent, the corresponding consent message is returned. +/// Otherwise, an empty string is returned. +/// +/// When a new country is added to the set of [countriesRequiringConsent], +/// update the [consentMessages] map. +String getCountryConsentMessage( + BuildContext context, { + required String? countryCode, +}) { + if (countryCode == null) { + return ''; + } + + final Map consentMessages = { + 'br': context.localization.labelCountryConsentBrazil, + // Add more countries and consent messages here. + }; + + return consentMessages[countryCode.toLowerCase()] ?? ''; +} + +/// Set of country codes that are not allowed to create an account. +const Set notAllowedCountryCodes = { + // MF country codes. + 'de', + 'es', + 'fr', + 'gr', + 'it', + 'lu', + 'mf', + // MLT country codes. + 'at', + 'be', + 'bg', + 'cy', + 'cz', + 'dk', + 'ee', + 'fi', + 'hr', + 'hu', + 'ie', + 'lt', + 'lv', + 'nl', + 'pl', + 'pt', + 'ro', + 'se', + 'si', + 'sk', + // MX country codes. + 'gb', + 'im', +}; + +/// [countriesRequiringConsent] is a set of country codes that are required +/// to show consent. +/// +/// If any country is required to show consent, add the country code +/// in lowercase to the set. +const Set countriesRequiringConsent = { + 'br', + // Add countries here. +}; diff --git a/packages/deriv_auth_ui/lib/src/core/helpers/endpoint_helper.dart b/packages/deriv_auth_ui/lib/src/core/helpers/endpoint_helper.dart new file mode 100644 index 000000000..c7ad6b961 --- /dev/null +++ b/packages/deriv_auth_ui/lib/src/core/helpers/endpoint_helper.dart @@ -0,0 +1,36 @@ +/// Default auth endpoint. +const String defaultAuthEndpoint = 'oauth.deriv.com'; + +/// Default ws endpoint. +const String defaultEndpoint = 'blue.binaryws.com'; + +/// Default app id. +const String defaultAppId = '23789'; + +/// Parses an [endpoint] argument and generates a url that points to QA servers +/// if [endpoint] starts with `qa` or a url that points to production servers if +/// [endpoint] contains `derivws` or `binaryws` and [isAuthUrl] is `true`. +/// +/// [endpoint] argument is required. +/// [isAuthUrl] is optional and has a default value of `false`. +String? generateEndpointUrl({ + required String? endpoint, + bool isAuthUrl = false, +}) { + if (endpoint == null) { + return null; + } + + final RegExp qaRegExp = RegExp('^(qa[0-9]+)\$', caseSensitive: false); + final RegExp derivRegExp = + RegExp('(binary|deriv)ws\.(com|app)\$', caseSensitive: false); + + if (isAuthUrl && derivRegExp.hasMatch(endpoint)) { + // Since Deriv app is under Deriv.app, the oauth url should be always `oauth.deriv.com`. + return defaultAuthEndpoint; + } else if (qaRegExp.hasMatch(endpoint)) { + return '$endpoint.deriv.dev'; + } + + return endpoint; +} diff --git a/packages/deriv_auth_ui/lib/src/core/helpers/semantic_labels.dart b/packages/deriv_auth_ui/lib/src/core/helpers/semantic_labels.dart new file mode 100644 index 000000000..b0f7e49df --- /dev/null +++ b/packages/deriv_auth_ui/lib/src/core/helpers/semantic_labels.dart @@ -0,0 +1,41 @@ +/// A class that contains semantic labels for various UI elements in the auth UI flow. +class SemanticsLabels { + // Signup Page + + /// A semantic label for the email field on the signup page. + static String signupEmailFieldSemantic = 'signup_email_field_semantic'; + + /// A semantic label for the signup button on the signup page. + static String signupButtonSemantic = 'signup_button_semantic'; + + /// A semantic label for the referral text field on the signup page. + static String signupReferralTextFieldSemantic = + 'signup_referral_field_semantic'; + + // Login Page + + /// A semantic label for the email field on the login page. + static String loginEmailFieldSemantic = 'login_email_field_semantic'; + + /// A semantic label for the password field on the login page. + static String loginPasswordFieldSemantic = 'login_password_field_semantic'; + + // Reset Password Page + + /// A semantic label for the email field on the reset password page. + static String resetPasswordEmailFieldSemantic = + 'reset_password_email_field_semantic'; + + /// A semantic label for the reset password button on the reset password page. + static String resetPasswordButtonSemantic = 'reset_password_button_semantic'; + + // Starter Page + + /// A semantic label for the signup button on the starter page. + static String starterPageSignupButtonSemantic = + 'starter_page_signup_button_semantic'; + + /// A semantic label for the login button on the starter page. + static String starterPageLoginButtonSemantic = + 'starter_page_login_button_semantic'; +} diff --git a/packages/deriv_auth_ui/lib/src/core/states/auth_error_state_handler.dart b/packages/deriv_auth_ui/lib/src/core/states/auth_error_state_handler.dart new file mode 100644 index 000000000..f7daa9184 --- /dev/null +++ b/packages/deriv_auth_ui/lib/src/core/states/auth_error_state_handler.dart @@ -0,0 +1,127 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +/// {@template default_auth_error_state_handler} +/// Base class for handling [DerivAuthErrorState]s. Client app can extend this +/// class and override the methods to handle the error states based on their +/// customization. +/// {@endtemplate} +base class AuthErrorStateHandler { + /// {@macro default_auth_error_state_handler} + AuthErrorStateHandler({ + required this.context, + }); + + /// The [BuildContext] of the widget that is using this handler. + final BuildContext context; + + /// On invalid 2FA code. + void invalid2faCode(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informInvalid2FACode, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// On account is not activated. + void onAccountUnavailable(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informDeactivatedAccount, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// On expired account. + void onExpiredAccount(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informExpiredAccount, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// On failed authorization. + void onFailedAuthorization(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informFailedAuthorization, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// User is trying to authenticate from an unsupported residence. + void onInavlidResidence(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informInvalidResidence, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// On invalid credentials. + void onInvalidCredentials(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informInvalidCredentials, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// User has set up 2FA and needs to enter 2FA. + void onMissingOtp(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informMissingOtp, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// On self closed account. + void onSelfClosed(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informSelfClosed, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// On unexpected error. + void onUnexpectedError(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informUnexpectedError, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// Account is not supported in the country. + void onUnsupportedCountry(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informUnsupportedCountry, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// On connection error. + void onConnectionError(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informConnectionError, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// On switch account error. + void onSwitchAccountError(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informSwitchAccountError, + actionLabel: context.localization.actionTryAgain, + ); + } +} diff --git a/packages/deriv_auth_ui/lib/src/core/states/auth_error_state_mapper.dart b/packages/deriv_auth_ui/lib/src/core/states/auth_error_state_mapper.dart new file mode 100644 index 000000000..ba6fb739e --- /dev/null +++ b/packages/deriv_auth_ui/lib/src/core/states/auth_error_state_mapper.dart @@ -0,0 +1,51 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth_ui/deriv_auth_ui.dart'; + +/// Maps the [DerivAuthErrorState] to the corresponding [AuthErrorStateHandler]. +void authErrorStateMapper({ + required DerivAuthErrorState authErrorState, + required AuthErrorStateHandler authErrorStateHandler, + List? ignoredExceptions, +}) { + if (ignoredExceptions?.contains(authErrorState.type) ?? false) { + return; + } + switch (authErrorState.type) { + case AuthErrorType.missingOtp: + authErrorStateHandler.onMissingOtp(authErrorState); + return; + case AuthErrorType.selfClosed: + authErrorStateHandler.onSelfClosed(authErrorState); + return; + case AuthErrorType.unsupportedCountry: + authErrorStateHandler.onUnsupportedCountry(authErrorState); + return; + case AuthErrorType.accountUnavailable: + authErrorStateHandler.onAccountUnavailable(authErrorState); + return; + case AuthErrorType.invalidCredential: + authErrorStateHandler.onInvalidCredentials(authErrorState); + return; + case AuthErrorType.invalid2faCode: + authErrorStateHandler.invalid2faCode(authErrorState); + return; + case AuthErrorType.failedAuthorization: + authErrorStateHandler.onFailedAuthorization(authErrorState); + return; + case AuthErrorType.invalidResidence: + authErrorStateHandler.onInavlidResidence(authErrorState); + return; + case AuthErrorType.expiredAccount: + authErrorStateHandler.onExpiredAccount(authErrorState); + return; + case AuthErrorType.connectionError: + authErrorStateHandler.onConnectionError(authErrorState); + return; + case AuthErrorType.switchAccountError: + authErrorStateHandler.onSwitchAccountError(authErrorState); + return; + default: + authErrorStateHandler.onUnexpectedError(authErrorState); + return; + } +} diff --git a/packages/deriv_auth_ui/lib/src/core/states/auth_state_listener.dart b/packages/deriv_auth_ui/lib/src/core/states/auth_state_listener.dart new file mode 100644 index 000000000..eee0ac6d6 --- /dev/null +++ b/packages/deriv_auth_ui/lib/src/core/states/auth_state_listener.dart @@ -0,0 +1,63 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// {@template auth_state_listener} +/// A [Widget] that listens to the [DerivAuthCubit] state changes. +/// This was created to make it easier to use [AuthErrorStateHandler] by +/// handling the mapping of auth error states with [AuthErrorStateHandler]'s method'. +/// {@endtemplate} +class DerivAuthStateListener extends StatelessWidget { + /// {@macro auth_state_listener} + const DerivAuthStateListener({ + required this.child, + super.key, + this.onLoggedIn, + this.onLoggedOut, + this.onLoading, + this.onError, + this.authErrorStateHandler, + }); + + /// The [Widget] that is using this [DerivAuthStateListener]. + final Widget child; + + /// Callback to be called when user is logged in. + final Function(DerivAuthLoggedInState)? onLoggedIn; + + /// Callback to be called when user is logged out. + final VoidCallback? onLoggedOut; + + /// Callback to be called when user is logging in. + final VoidCallback? onLoading; + + /// Callback to be called when an error occurs. + final Function(DerivAuthErrorState)? onError; + + /// Extension of base [AuthErrorStateHandler]. If not provided, base implementation will be used. + final AuthErrorStateHandler? authErrorStateHandler; + + @override + Widget build(BuildContext context) => + BlocListener( + listener: (BuildContext context, DerivAuthState state) { + if (state is DerivAuthLoggedInState) { + onLoggedIn?.call(state); + } else if (state is DerivAuthLoggedOutState) { + onLoggedOut?.call(); + } else if (state is DerivAuthLoadingState) { + onLoading?.call(); + } else if (state is DerivAuthErrorState) { + onError?.call(state); + + authErrorStateMapper( + authErrorState: state, + authErrorStateHandler: authErrorStateHandler ?? + AuthErrorStateHandler(context: context), + ); + } + }, + child: child, + ); +} diff --git a/packages/deriv_auth_ui/lib/src/core/states/states.dart b/packages/deriv_auth_ui/lib/src/core/states/states.dart new file mode 100644 index 000000000..7545c9d18 --- /dev/null +++ b/packages/deriv_auth_ui/lib/src/core/states/states.dart @@ -0,0 +1,2 @@ +export 'auth_error_state_mapper.dart'; +export 'auth_error_state_handler.dart'; diff --git a/packages/deriv_auth_ui/lib/src/features/get_started/layouts/deriv_get_started_layout.dart b/packages/deriv_auth_ui/lib/src/features/get_started/layouts/deriv_get_started_layout.dart index 160d26910..de6ce0670 100644 --- a/packages/deriv_auth_ui/lib/src/features/get_started/layouts/deriv_get_started_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/get_started/layouts/deriv_get_started_layout.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:math' as math; import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; +import 'package:deriv_auth_ui/src/core/helpers/semantic_labels.dart'; import 'package:deriv_auth_ui/src/features/get_started/models/deriv_get_started_slide_model.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; @@ -20,6 +21,7 @@ class DerivGetStartedLayout extends StatefulWidget { required this.backgroundImagePath, required this.onLoginTapped, required this.onSignupTapped, + required this.onTapNavigation, Key? key, }) : super(key: key); @@ -38,6 +40,9 @@ class DerivGetStartedLayout extends StatefulWidget { /// Callback to be called when signup button is tapped. final VoidCallback onSignupTapped; + /// Navigation to be called when screen is tapped seven times. + final VoidCallback onTapNavigation; + @override State createState() => _DerivGetStartedLayoutState(); } @@ -102,7 +107,9 @@ class _DerivGetStartedLayoutState extends State { PreferredSizeWidget _buildAppBar(BuildContext context) => AppBar( backgroundColor: context.theme.colors.secondary, centerTitle: false, - title: SvgPicture.asset(widget.appLogoIconPath), + title: AppSettingGestureDetector( + onTapNavigation: widget.onTapNavigation, + child: SvgPicture.asset(widget.appLogoIconPath)), ); Timer _buildNewScrollTimer() => Timer.periodic( @@ -133,26 +140,34 @@ class _DerivGetStartedLayoutState extends State { Widget _buildButtons() => Column( mainAxisSize: MainAxisSize.min, children: [ - PrimaryButton( - onPressed: widget.onSignupTapped, - child: Center( - child: Text( - context.localization.actionGetAFreeAccount, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent, + Semantics( + explicitChildNodes: true, + label: SemanticsLabels.starterPageSignupButtonSemantic, + child: PrimaryButton( + onPressed: widget.onSignupTapped, + child: Center( + child: Text( + context.localization.actionGetAFreeAccount, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), ), ), ), ), - SecondaryButton( - onPressed: widget.onLoginTapped, - child: Center( - child: Text( - context.localization.actionLogin, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent, + Semantics( + explicitChildNodes: true, + label: SemanticsLabels.starterPageLoginButtonSemantic, + child: SecondaryButton( + onPressed: widget.onLoginTapped, + child: Center( + child: Text( + context.localization.actionLogin, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), ), ), ), @@ -221,7 +236,7 @@ class _DerivGetStartedLayoutState extends State { supportingText, style: context.theme.textStyle( textStyle: TextStyles.title, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), textAlign: TextAlign.center, ), diff --git a/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_2fa_layout.dart b/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_2fa_layout.dart index 00009622c..4634aaef3 100644 --- a/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_2fa_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_2fa_layout.dart @@ -63,7 +63,7 @@ class _Deriv2FALayoutState extends State { context.localization.informEnterTwoFactorAuthCode, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), textAlign: TextAlign.center, ), diff --git a/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_login_layout.dart b/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_login_layout.dart index 4f89a1763..9671a72a8 100644 --- a/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_login_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_login_layout.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; +import 'package:deriv_auth_ui/src/core/helpers/semantic_labels.dart'; import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_divider.dart'; import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_panel.dart'; import 'package:deriv_theme/deriv_theme.dart'; @@ -16,11 +18,13 @@ class DerivLoginLayout extends StatefulWidget { const DerivLoginLayout({ required this.onResetPassTapped, required this.onSignupTapped, - required this.onLoginError, required this.onLoggedIn, required this.onSocialAuthButtonPressed, required this.welcomeLabel, required this.greetingLabel, + this.isSocialAuthEnabled = true, + this.authErrorStateHandler, + this.onLoginError, this.onLoginTapped, Key? key, }) : super(key: key); @@ -31,8 +35,11 @@ class DerivLoginLayout extends StatefulWidget { /// Callback to be called when signup button is tapped. final VoidCallback onSignupTapped; - /// Callback to be called when login button is tapped. - final Function(DerivAuthErrorState) onLoginError; + /// Extension of base [AuthErrorStateHandler]. If not provided, base implementation will be used. + final AuthErrorStateHandler? authErrorStateHandler; + + /// Callback to be called when login error occurs. + final Function(DerivAuthErrorState)? onLoginError; /// Callback to be called when user is logged in. final Function(DerivAuthLoggedInState) onLoggedIn; @@ -49,6 +56,9 @@ class DerivLoginLayout extends StatefulWidget { /// Greeting text to be displayed on login page. final String greetingLabel; + /// Whether to display social auth buttons. + final bool isSocialAuthEnabled; + @override State createState() => _DerivLoginLayoutState(); } @@ -108,13 +118,17 @@ class _DerivLoginLayoutState extends State { const SizedBox(height: ThemeProvider.margin24), DerivSocialAuthDivider( label: context.localization.informLoginOptions, + isVisible: widget.isSocialAuthEnabled, ), - const SizedBox(height: ThemeProvider.margin24), + if (widget.isSocialAuthEnabled) + const SizedBox(height: ThemeProvider.margin24), DerivSocialAuthPanel( onSocialAuthButtonPressed: widget.onSocialAuthButtonPressed, + isVisible: widget.isSocialAuthEnabled, ), - const SizedBox(height: ThemeProvider.margin24), + if (widget.isSocialAuthEnabled) + const SizedBox(height: ThemeProvider.margin24), _buildFooterSection(), ], ), @@ -138,13 +152,14 @@ class _DerivLoginLayoutState extends State { widget.greetingLabel, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ]; List _buildTextFields({required bool isEnabled}) => [ BaseTextField( + semanticLabel: SemanticsLabels.loginEmailFieldSemantic, controller: _emailController, focusNode: _emailFocusNode, labelText: context.localization.labelEmail, @@ -159,6 +174,7 @@ class _DerivLoginLayoutState extends State { ), const SizedBox(height: ThemeProvider.margin32), BaseTextField( + semanticLabel: SemanticsLabels.loginPasswordFieldSemantic, controller: _passwordController, focusNode: _passwordFocusNode, labelText: context.localization.labelPassword, @@ -240,7 +256,7 @@ class _DerivLoginLayoutState extends State { context.localization.labelDontHaveAnAccountYet, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), InkWell( @@ -262,7 +278,13 @@ class _DerivLoginLayoutState extends State { void _onAuthState(BuildContext context, DerivAuthState state) { if (state is DerivAuthErrorState) { - widget.onLoginError.call(state); + widget.onLoginError?.call(state); + + authErrorStateMapper( + authErrorState: state, + authErrorStateHandler: widget.authErrorStateHandler ?? + AuthErrorStateHandler(context: context), + ); } if (state is DerivAuthLoggedInState) { diff --git a/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_divider.dart b/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_divider.dart index 64d14f557..1f9411517 100644 --- a/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_divider.dart +++ b/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_divider.dart @@ -6,29 +6,37 @@ class DerivSocialAuthDivider extends StatelessWidget { /// Initializes the class. const DerivSocialAuthDivider({ required this.label, + this.isVisible = true, Key? key, }) : super(key: key); /// The label that displayed in the divider. final String label; + /// Whether the buttons are visible. + /// Defaults to `true`. Acts as a flag to hide the buttons. + final bool isVisible; + @override - Widget build(BuildContext context) => Row( - children: [ - _buildDivider(context), - Padding( - padding: - const EdgeInsets.symmetric(horizontal: ThemeProvider.margin08), - child: Text( - label, - style: context.theme.textStyle( - textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + Widget build(BuildContext context) => Visibility( + visible: isVisible, + child: Row( + children: [ + _buildDivider(context), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin08), + child: Text( + label, + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.general, + ), ), ), - ), - _buildDivider(context), - ], + _buildDivider(context), + ], + ), ); Widget _buildDivider(BuildContext context) => Expanded( diff --git a/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_panel.dart b/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_panel.dart index 91298a3d4..6100b7bbf 100644 --- a/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_panel.dart +++ b/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_panel.dart @@ -10,6 +10,7 @@ class DerivSocialAuthPanel extends StatelessWidget { const DerivSocialAuthPanel({ required this.onSocialAuthButtonPressed, this.isEnabled = true, + this.isVisible = true, Key? key, }) : super(key: key); @@ -19,19 +20,26 @@ class DerivSocialAuthPanel extends StatelessWidget { /// Defaults to `true`. final bool isEnabled; + /// Whether the buttons are visible. + /// Defaults to `true`. Acts as a flag to hide the buttons. + final bool isVisible; + /// onPressed callback for social auth buttons. final void Function(SocialAuthProvider) onSocialAuthButtonPressed; @override - Widget build(BuildContext context) => Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildSocialAuthButton(SocialAuthProvider.apple), - const SizedBox(width: ThemeProvider.margin24), - _buildSocialAuthButton(SocialAuthProvider.google), - const SizedBox(width: ThemeProvider.margin24), - _buildSocialAuthButton(SocialAuthProvider.facebook), - ], + Widget build(BuildContext context) => Visibility( + visible: isVisible, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildSocialAuthButton(SocialAuthProvider.apple), + const SizedBox(width: ThemeProvider.margin24), + _buildSocialAuthButton(SocialAuthProvider.google), + const SizedBox(width: ThemeProvider.margin24), + _buildSocialAuthButton(SocialAuthProvider.facebook), + ], + ), ); Widget _buildSocialAuthButton(SocialAuthProvider socialAuthProvider) => diff --git a/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_choose_new_pass_layout.dart b/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_choose_new_pass_layout.dart index 787a508d9..5785745f8 100644 --- a/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_choose_new_pass_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_choose_new_pass_layout.dart @@ -36,12 +36,9 @@ class DerivChooseNewPassLayout extends StatefulWidget { } class _DerivChooseNewPassLayoutState extends State { - static const Duration _successPageHoldDuration = Duration(seconds: 2); - final GlobalKey _formKey = GlobalKey(); final TextEditingController _passController = TextEditingController(); final FocusNode _passFocusNode = FocusNode(); - final PageController _pageController = PageController(); bool _isBusy = false; bool _isPasswordVisible = false; @@ -59,25 +56,12 @@ class _DerivChooseNewPassLayoutState extends State { body: BlocListener( listener: (BuildContext context, DerivResetPassState state) { if (state is DerivResetPassPasswordChangedState) { - _pageController.animateToPage( - 1, - duration: slidingPageChangeDuration, - curve: Curves.easeInOut, - ); - - Timer(_successPageHoldDuration, widget.onResetPassSucceed); + widget.onResetPassSucceed(); } else if (state is DerivResetPassErrorState) { widget.onResetPassError(state.errorMessage); } }, - child: PageView( - controller: _pageController, - physics: const NeverScrollableScrollPhysics(), - children: [ - _buildChooseNewPassSection(context), - _buildSuccessPassChangeSection(context) - ], - ), + child: _buildChooseNewPassSection(context), ), ); @@ -92,7 +76,7 @@ class _DerivChooseNewPassLayoutState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: ThemeProvider.margin72), - Expanded(child: _buildContent()), + _buildContent(), const SizedBox(height: ThemeProvider.margin24), _buildSubmitPassButton() ], @@ -158,44 +142,6 @@ class _DerivChooseNewPassLayoutState extends State { ), ); - Widget _buildSuccessPassChangeSection(BuildContext context) => Center( - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: ThemeProvider.margin16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox( - height: ThemeProvider.margin16, - width: ThemeProvider.margin16, - child: LoadingIndicator( - valueColor: Colors.white, - strokeWidth: 2.5, - ), - ), - const SizedBox( - height: ThemeProvider.margin16, - ), - Text( - context.localization.informYourPassHasBeenReset, - style: TextStyles.title, - ), - const SizedBox( - height: ThemeProvider.margin08, - ), - Text( - context.localization.informRedirectLogin, - textAlign: TextAlign.center, - style: context.theme.textStyle( - textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, - ), - ), - ], - ), - ), - ); - Widget _buildSubmitPassButton() => ElevatedButton( style: ButtonStyle( backgroundColor: MaterialStateProperty.all( @@ -252,7 +198,6 @@ class _DerivChooseNewPassLayoutState extends State { @override void dispose() { - _pageController.dispose(); _passController.dispose(); _passFocusNode.dispose(); diff --git a/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_reset_pass_layout.dart b/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_reset_pass_layout.dart index 4614866aa..0a4ee0159 100644 --- a/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_reset_pass_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_reset_pass_layout.dart @@ -4,6 +4,7 @@ import 'package:deriv_auth/deriv_auth.dart'; import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; +import 'package:deriv_auth_ui/src/core/helpers/semantic_labels.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -95,7 +96,7 @@ class _DerivResetPassLayoutState extends State { textAlign: TextAlign.center, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), const SizedBox(height: kToolbarHeight), @@ -129,12 +130,13 @@ class _DerivResetPassLayoutState extends State { context.localization.informResetPassByEmail, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), const SizedBox(height: ThemeProvider.margin24), BaseTextField( + semanticLabel: SemanticsLabels.resetPasswordEmailFieldSemantic, controller: _emailController, focusNode: _emailFocusNode, labelText: context.localization.labelEmail, @@ -152,32 +154,36 @@ class _DerivResetPassLayoutState extends State { ), ); - Widget _buildSubmitEmailButton() => ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - context.theme.colors.coral.withOpacity( - getOpacity(isEnabled: _isFormValid()), + Widget _buildSubmitEmailButton() => Semantics( + explicitChildNodes: true, + label: SemanticsLabels.resetPasswordButtonSemantic, + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + context.theme.colors.coral.withOpacity( + getOpacity(isEnabled: _isFormValid()), + ), ), ), - ), - onPressed: _isFormValid() ? _onSubmitEmailTapped : null, - child: Center( - child: _isBusy - ? LoadingIndicator( - valueColor: context.theme.colors.prominent, - strokeWidth: ThemeProvider.margin02, - height: ThemeProvider.margin16, - width: ThemeProvider.margin16, - ) - : Text( - context.localization.actionResetPass, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent.withOpacity( - getOpacity(isEnabled: _isFormValid()), + onPressed: _isFormValid() ? _onSubmitEmailTapped : null, + child: Center( + child: _isBusy + ? LoadingIndicator( + valueColor: context.theme.colors.prominent, + strokeWidth: ThemeProvider.margin02, + height: ThemeProvider.margin16, + width: ThemeProvider.margin16, + ) + : Text( + context.localization.actionResetPass, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent.withOpacity( + getOpacity(isEnabled: _isFormValid()), + ), ), ), - ), + ), ), ); diff --git a/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_success_pass_change_layout.dart b/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_success_pass_change_layout.dart new file mode 100644 index 000000000..b0af4c580 --- /dev/null +++ b/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_success_pass_change_layout.dart @@ -0,0 +1,59 @@ +import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/presentation/widgets/loading_indicator.dart'; +import 'package:flutter/material.dart'; + +/// Success pass change page layout. +class DerivSuccessPassChangeLayout extends StatelessWidget { + /// Initializes success pass change page. + const DerivSuccessPassChangeLayout({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => Scaffold( + backgroundColor: context.theme.colors.primary, + appBar: AppBar( + elevation: ThemeProvider.zeroMargin, + title: Text( + context.localization.labelResetPassword, + style: TextStyles.title, + ), + ), + body: Center( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: ThemeProvider.margin16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: ThemeProvider.margin16, + width: ThemeProvider.margin16, + child: LoadingIndicator( + valueColor: Colors.white, + strokeWidth: 2.5, + ), + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + Text( + context.localization.informYourPassHasBeenReset, + style: TextStyles.title, + ), + const SizedBox( + height: ThemeProvider.margin08, + ), + Text( + context.localization.informRedirectLogin, + textAlign: TextAlign.center, + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.general, + ), + ), + ], + ), + ), + ), + ); +} diff --git a/packages/deriv_auth_ui/lib/src/features/setting_page/layouts/deriv_setting_layout.dart b/packages/deriv_auth_ui/lib/src/features/setting_page/layouts/deriv_setting_layout.dart new file mode 100644 index 000000000..937970849 --- /dev/null +++ b/packages/deriv_auth_ui/lib/src/features/setting_page/layouts/deriv_setting_layout.dart @@ -0,0 +1,210 @@ +import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; +import 'package:deriv_auth_ui/src/core/helpers/endpoint_helper.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/utils/regex_helpers.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +/// Login setting page +/// +/// This page is used to apply necessary QA configurations for login process +/// Two fields can be set in this page 'endpoint' and 'app_id' +/// The applied values stored for future usage +class DerivSettingLayout extends StatefulWidget { + /// Login Setting Page Constructor + const DerivSettingLayout({ + required this.updateFlavorConfigs, + required this.appLabel, + required this.saveValues, + this.endpoint = defaultEndpoint, + this.appId = defaultAppId, + this.devApp = 'com.deriv.app.dev', + this.stagingApp = 'com.deriv.app.staging', + this.getAppEnv, + this.setAppEnv, + Key? key, + }) : super(key: key); + + /// Update flavor configurations + final Future updateFlavorConfigs; + + /// Save values to shared preferences + final Function({required String endpoint, required String appId}) saveValues; + + /// End Point + final String endpoint; + + /// App Id + final String appId; + + /// Application label + final String appLabel; + + /// Dev flavor app. + final String devApp; + + /// Staging flavor app. + final String stagingApp; + + /// Gets environment variable + final Future? getAppEnv; + + /// Sets environment variable + final Future Function({required bool value})? setAppEnv; + + @override + _SettingsPageState createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + final TextEditingController _endpointController = TextEditingController(); + final TextEditingController _appIdController = TextEditingController(); + + final GlobalKey _formKey = GlobalKey(); + late Future packageInfo; + + @override + void initState() { + super.initState(); + packageInfo = PackageInfo.fromPlatform(); + + _endpointController.text = widget.endpoint; + _appIdController.text = widget.appId; + } + + @override + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + // Save Values to shared preferences + widget.saveValues( + endpoint: _endpointController.text.isNotEmpty + ? _endpointController.text + : defaultEndpoint, + appId: _appIdController.text.isNotEmpty + ? _appIdController.text + : defaultAppId, + ); + + // Update Flavor Configurations before dismissing the page + await widget.updateFlavorConfigs; + return true; + }, + child: Scaffold( + appBar: AppBar( + elevation: ThemeProvider.zeroMargin, + title: Text(widget.appLabel), + ), + body: Form( + key: _formKey, + child: ListView( + children: [ + _title, + const SizedBox(height: ThemeProvider.margin16), + _endpoint, + const SizedBox(height: ThemeProvider.margin16), + _appId, + const SizedBox(width: ThemeProvider.margin08), + _buildEnvironmentSwitcher, + ], + ), + ), + ), + ); + + Widget get _buildEnvironmentSwitcher => FutureBuilder( + future: packageInfo, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData && + (snapshot.data?.packageName == widget.devApp || + snapshot.data?.packageName == widget.stagingApp)) { + return Padding( + padding: + const EdgeInsets.symmetric(horizontal: ThemeProvider.margin16), + child: Row( + children: [ + const Text('Production environment'), + const SizedBox(width: ThemeProvider.margin08), + FutureBuilder( + future: widget.getAppEnv, + builder: + (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData && widget.setAppEnv != null) { + return Switch( + value: snapshot.data ?? false, + onChanged: (bool val) { + setState(() { + widget.setAppEnv!(value: val); + }); + }, + ); + } + return const SizedBox.shrink(); + }) + ], + ), + ); + } + return const SizedBox.shrink(); + }); + + Widget get _title => Padding( + padding: const EdgeInsets.only( + left: ThemeProvider.margin12, + top: ThemeProvider.margin24, + ), + child: Text( + context.localization.labelDeveloper, + style: context.theme.textStyle( + textStyle: TextStyles.body1Bold, + color: context.theme.colors.coral, + ), + ), + ); + + Widget get _endpoint => _buildTextInputField( + label: context.localization.labelEndpoint, + semantic: context.localization.semanticEndpoint, + controller: _endpointController, + validator: (String? value) => hasOnlySmallLettersAndNumberInput(value!) + ? null + : context.localization.warnInvalidEndpoint, + ); + + Widget get _appId => _buildTextInputField( + label: context.localization.labelApplicationID, + semantic: context.localization.semanticApplicationID, + controller: _appIdController, + validator: (String? value) => hasOnlyNumberInput(value!) + ? null + : context.localization.warnInvalidApplicationID, + ); + + Widget _buildTextInputField({ + required String label, + required String semantic, + required TextEditingController controller, + required FormFieldValidator validator, + }) => + Padding( + padding: const EdgeInsets.symmetric(horizontal: ThemeProvider.margin16), + child: Semantics( + label: semantic, + explicitChildNodes: true, + child: TextFormField( + decoration: InputDecoration( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: context.theme.colors.lessProminent, + ), + ), + labelText: label, + labelStyle: TextStyle(color: context.theme.colors.lessProminent), + border: const OutlineInputBorder(), + ), + controller: controller, + onChanged: (_) => _formKey.currentState!.validate(), + validator: validator, + ), + ), + ); +} diff --git a/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_cubit.dart b/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_cubit.dart index 4ce13603a..49a5d7a9a 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_cubit.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_cubit.dart @@ -1,3 +1,4 @@ +import 'package:deriv_auth_ui/src/core/helpers/country_selection_helper.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:deriv_auth_ui/src/features/signup/models/deriv_residence_model.dart'; @@ -14,53 +15,53 @@ class DerivCountrySelectionCubit extends Cubit { final Future> residences; /// Fetches residence countries. - Future fetchResidenceCounties() async { + Future fetchResidenceCountries() async { final List countries = await residences; final List filteredCountries = countries .where( - (DerivResidenceModel country) => _isAllowedCountry(country), + (DerivResidenceModel country) => + isAllowedCountry(countryCode: country.code), ) .toList(); emit(DerivCountrySelectionLoadedState(filteredCountries)); } - bool _isAllowedCountry(DerivResidenceModel country) => _notAllowedCountryCodes - .every((String countryCode) => country.code != countryCode); + /// Changes the selected country and updates the state accordingly. + /// + /// If [selectedCountry] is not `null`, this method emits a [DerivCountryChangedState] + /// with the updated selected country. + /// + /// This method does not perform any action if [selectedCountry] is `null`. + Future changeSelectedCountry({ + DerivResidenceModel? selectedCountry, + }) async { + if (selectedCountry != null) { + emit( + DerivCountryChangedState( + state.countries, + selectedCountry: selectedCountry, + selectedCountryRequiresConsent: isConsentRequired( + countryCode: selectedCountry.code, + ), + ), + ); + } + } - static final List _notAllowedCountryCodes = [ - // MF country codes. - 'de', - 'es', - 'fr', - 'gr', - 'it', - 'lu', - 'mf', - // MLT country codes. - 'at', - 'be', - 'bg', - 'cy', - 'cz', - 'dk', - 'ee', - 'fi', - 'hr', - 'hu', - 'ie', - 'lt', - 'lv', - 'nl', - 'pl', - 'pt', - 'ro', - 'se', - 'si', - 'sk', - // MX country codes. - 'gb', - 'im' - ]; + /// Updates the country consent status and triggers a state change. + /// + /// [agreedToTerms]: Whether the user has agreed to the terms for the + /// selected country. + Future updateCountryConsentStatus({bool? agreedToTerms = false}) async { + emit( + DerivCountryConsentChangedState( + state.countries, + selectedCountry: state.selectedCountry, + selectedCountryRequiresConsent: state.selectedCountryRequiresConsent, + agreedToTerms: agreedToTerms, + ), + ); + } } diff --git a/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_state.dart b/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_state.dart index 6f67179df..ed48f1844 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_state.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_state.dart @@ -3,28 +3,82 @@ part of 'deriv_country_selection_cubit.dart'; /// Country selection state abstract class DerivCountrySelectionState extends Equatable { /// Initialize country selection state. - const DerivCountrySelectionState(this.countries); + const DerivCountrySelectionState({ + this.countries = const [], + this.selectedCountry, + this.agreedToTerms = false, + this.selectedCountryRequiresConsent = false, + }); /// List of countries. final List countries; + + /// Selected country. + final DerivResidenceModel? selectedCountry; + + /// If the selected country requires consent, value must be true to continue. + /// Default is null. + /// Example: For Brazil, Brazil requires consent to continue. + /// The user must agree to the terms to continue. + final bool agreedToTerms; + + /// If the selected country requires consent to continue, value is true. + /// Default is false. + final bool selectedCountryRequiresConsent; + + @override + List get props => [ + countries, + selectedCountry, + agreedToTerms, + selectedCountryRequiresConsent, + ]; } /// Initial state. class DerivCountrySelectionInitialState extends DerivCountrySelectionState { - /// Initializes initial state. + /// Initialises initial state. const DerivCountrySelectionInitialState() - : super(const []); - - @override - List get props => []; + : super(countries: const []); } /// Country list loaded state. class DerivCountrySelectionLoadedState extends DerivCountrySelectionState { - /// Initialize country list loaded state - const DerivCountrySelectionLoadedState(List countries) - : super(countries); + /// Initialise country list loaded state + const DerivCountrySelectionLoadedState( + List countries, { + DerivResidenceModel? selectedCountry, + }) : super(countries: countries, selectedCountry: selectedCountry); +} - @override - List get props => [countries]; +/// Country selection changed state. +class DerivCountryChangedState extends DerivCountrySelectionState { + /// Initialise country selection changed state. + const DerivCountryChangedState( + List countries, { + DerivResidenceModel? selectedCountry, + bool? selectedCountryRequiresConsent, + }) : super( + countries: countries, + selectedCountry: selectedCountry, + selectedCountryRequiresConsent: + selectedCountryRequiresConsent ?? false, + ); +} + +/// State to update country selection consent. +class DerivCountryConsentChangedState extends DerivCountrySelectionState { + /// Initialize country list loaded state + const DerivCountryConsentChangedState( + List countries, { + DerivResidenceModel? selectedCountry, + bool? selectedCountryRequiresConsent, + bool? agreedToTerms, + }) : super( + countries: countries, + selectedCountry: selectedCountry, + agreedToTerms: agreedToTerms ?? false, + selectedCountryRequiresConsent: + selectedCountryRequiresConsent ?? false, + ); } diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_country_selection_layout.dart b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_country_selection_layout.dart index 01ccde127..258cac866 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_country_selection_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_country_selection_layout.dart @@ -1,5 +1,6 @@ import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; +import 'package:deriv_auth_ui/src/core/helpers/country_selection_helper.dart'; import 'package:deriv_auth_ui/src/features/signup/cubits/deriv_country_selection_cubit.dart'; import 'package:deriv_auth_ui/src/features/signup/models/deriv_residence_model.dart'; import 'package:deriv_auth_ui/src/features/signup/widgets/country_selection_list_widget.dart'; @@ -17,6 +18,7 @@ class DerivCountrySelectionLayout extends StatefulWidget { required this.residences, required this.onNextPressed, this.affiliateToken, + this.countryConsentMessage, Key? key, }) : super(key: key); @@ -32,6 +34,9 @@ class DerivCountrySelectionLayout extends StatefulWidget { /// Affiliate token. final String? affiliateToken; + /// Message to be shown beside country consent checkbox. + final String? countryConsentMessage; + @override State createState() => _DerivCountrySelectionLayoutState(); @@ -45,14 +50,13 @@ class _DerivCountrySelectionLayoutState late TextEditingController _textController; late final DerivCountrySelectionCubit _countrySelectionCubit; - DerivResidenceModel? _selectedResidence; @override void initState() { super.initState(); _countrySelectionCubit = DerivCountrySelectionCubit(widget.residences) - ..fetchResidenceCounties(); + ..fetchResidenceCountries(); _textController = TextEditingController(); } @@ -61,15 +65,29 @@ class _DerivCountrySelectionLayoutState Widget build(BuildContext context) => Scaffold( backgroundColor: context.theme.colors.primary, body: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Expanded(child: _buildUpperPage()), - Padding( - padding: const EdgeInsets.all(ThemeProvider.margin16), - child: _buildLowerPage(), - ), - ], + child: BlocListener( + bloc: _countrySelectionCubit, + listener: (BuildContext context, DerivCountrySelectionState state) { + if (state.selectedCountry != null && + state.selectedCountry?.name != null) { + _textController.text = state.selectedCountry?.name ?? ''; + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + _formKey.currentState!.validate(); + }); + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded(child: _buildUpperPage()), + Padding( + padding: const EdgeInsets.all(ThemeProvider.margin16), + child: _buildLowerPage(), + ), + ], + ), ), ), ); @@ -86,7 +104,11 @@ class _DerivCountrySelectionLayoutState style: TextStyles.title, ), const SizedBox(height: ThemeProvider.margin24), - _buildSelectionInput() + _buildSelectionInput(), + const SizedBox(height: ThemeProvider.margin16), + _buildCountryConsentCheckbox( + countryConsentMessage: widget.countryConsentMessage, + ), ], ), ); @@ -114,24 +136,31 @@ class _DerivCountrySelectionLayoutState color: context.theme.colors.prominent, ), readOnly: true, - enabled: state is DerivCountrySelectionLoadedState, - validator: (String? value) => _selectedResidence!.isDisabled - ? context.localization.warnCountryNotAvailable - : null, + enabled: _shouldEnableCountrySelectionField(state), + validator: (String? value) => _countrySelectionValidator(context, + selectedCountry: state.selectedCountry), onTap: () => _onSelectCountryTap(state.countries), ), ), ); - Widget _buildNextButton() => PrimaryButton( - isEnabled: - _selectedResidence != null && !_selectedResidence!.isDisabled, - onPressed: widget.onNextPressed, - child: Center( - child: Text( - context.localization.actionNext, - style: TextStyles.button - .copyWith(color: context.theme.colors.prominent), + Widget _buildNextButton() => + BlocBuilder( + bloc: _countrySelectionCubit, + builder: (BuildContext context, DerivCountrySelectionState state) => + PrimaryButton( + isEnabled: _shouldEnableNextButton( + state.selectedCountry, + isConsentRequired: state.selectedCountryRequiresConsent, + agreedToTerms: state.agreedToTerms, + ), + onPressed: widget.onNextPressed, + child: Center( + child: Text( + context.localization.actionNext, + style: TextStyles.button + .copyWith(color: context.theme.colors.prominent), + ), ), ), ); @@ -150,10 +179,9 @@ class _DerivCountrySelectionLayoutState countries: countries, onChanged: (int index) => setState( () { - _textController.text = countries[index].name; - _selectedResidence = countries[index]; - - _formKey.currentState!.validate(); + _countrySelectionCubit.changeSelectedCountry( + selectedCountry: countries[index], + ); }, ), ), @@ -161,6 +189,88 @@ class _DerivCountrySelectionLayoutState ); } + Widget _buildCountryConsentCheckbox({String? countryConsentMessage}) => + BlocBuilder( + bloc: _countrySelectionCubit, + builder: (BuildContext context, DerivCountrySelectionState state) { + final DerivResidenceModel? selectedCountry = state.selectedCountry; + + if (selectedCountry == null || + !state.selectedCountryRequiresConsent) { + return const SizedBox.shrink(); + } + + return CustomCheckbox( + padding: const EdgeInsets.symmetric( + vertical: ThemeProvider.margin16, + ), + contentsVerticalAlignment: CrossAxisAlignment.start, + value: state.agreedToTerms, + onValueChanged: ({bool? isChecked}) => + _countrySelectionCubit.updateCountryConsentStatus( + agreedToTerms: isChecked, + ), + message: countryConsentMessage ?? + getCountryConsentMessage( + context, + countryCode: selectedCountry.code, + ), + ); + }, + ); + + /// Validates the country selection. + String? _countrySelectionValidator( + BuildContext context, { + DerivResidenceModel? selectedCountry, + }) { + if (selectedCountry != null && selectedCountry.isDisabled) { + return context.localization.warnCountryNotAvailable; + } + + return null; + } + + /// Determines whether the next button should be enabled based on country selection. + /// + /// Returns `true` if the following conditions are met: + /// - A country is selected. + /// - The selected country is not disabled. + /// - If `isConsentRequired` is true, the user must agree to the terms. + /// + /// Otherwise, returns `false`. + bool _shouldEnableNextButton( + DerivResidenceModel? selectedCountry, { + bool agreedToTerms = false, + bool isConsentRequired = false, + }) { + if (selectedCountry == null) { + return false; + } + + bool shouldEnable = !selectedCountry.isDisabled; + + if (isConsentRequired) { + shouldEnable = shouldEnable && agreedToTerms; + } + + return shouldEnable; + } + + /// Determines whether the country selection field should be enabled + /// based on the given state. + bool _shouldEnableCountrySelectionField( + DerivCountrySelectionState countrySelectionState, + ) { + if (countrySelectionState is DerivCountrySelectionLoadedState || + countrySelectionState is DerivCountryChangedState || + countrySelectionState is DerivCountryConsentChangedState) { + return true; + } + + return false; + } + @override void dispose() { _focusNode.dispose(); diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_email_not_received_layout.dart b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_email_not_received_layout.dart index bad1e2728..a7dc2d4ec 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_email_not_received_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_email_not_received_layout.dart @@ -45,7 +45,7 @@ class DerivEmailNotReceivedLayout extends StatelessWidget { context.localization.labelEmailIssueHeader, style: context.theme.textStyle( textStyle: TextStyles.body2, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), const SizedBox(height: ThemeProvider.margin24), @@ -58,7 +58,7 @@ class DerivEmailNotReceivedLayout extends StatelessWidget { context.localization.labelEmailIssueSpam, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), @@ -74,7 +74,7 @@ class DerivEmailNotReceivedLayout extends StatelessWidget { context.localization.labelEmailIssueWrongEmail, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), @@ -90,7 +90,7 @@ class DerivEmailNotReceivedLayout extends StatelessWidget { context.localization.labelEmailIssueTypo, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), @@ -106,7 +106,7 @@ class DerivEmailNotReceivedLayout extends StatelessWidget { context.localization.labelEmailIssueFirewall, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_set_password_layout.dart b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_set_password_layout.dart index dc4469142..63e5e359f 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_set_password_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_set_password_layout.dart @@ -1,8 +1,9 @@ import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; -import 'package:deriv_auth_ui/src/features/signup/models/deriv_auth_utm_model.dart'; +import 'package:deriv_auth_ui/src/core/states/auth_state_listener.dart'; import 'package:deriv_auth_ui/src/features/signup/widgets/password_policy_checker_widget.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; @@ -14,12 +15,13 @@ import 'package:flutter_svg/flutter_svg.dart'; class DerivSetPasswordLayout extends StatefulWidget { /// constructor of country set password page const DerivSetPasswordLayout({ - required this.onDerivAuthState, required this.onDerivSignupState, required this.onPreviousPressed, required this.verificationCode, required this.residence, + this.authErrorStateHandler, this.utmModel, + this.onAuthError, Key? key, }) : super(key: key); @@ -32,8 +34,11 @@ class DerivSetPasswordLayout extends StatefulWidget { /// Utm model final DerivAuthUtmModel? utmModel; - /// Callback to be called when auth state changes. - final void Function(BuildContext, DerivAuthState) onDerivAuthState; + /// Extension of base [AuthErrorStateHandler]. If not provided, base implementation will be used. + final AuthErrorStateHandler? authErrorStateHandler; + + /// Callback to be called on [DerivAuthErrorState] + final Function(DerivAuthErrorState)? onAuthError; /// Callback to be called when signup state changes. final void Function(BuildContext, DerivSignupState) onDerivSignupState; @@ -63,9 +68,9 @@ class _DerivSetPasswordLayoutState extends State { } @override - Widget build(BuildContext context) => - BlocListener( - listener: widget.onDerivAuthState, + Widget build(BuildContext context) => DerivAuthStateListener( + authErrorStateHandler: widget.authErrorStateHandler, + onError: widget.onAuthError, child: Scaffold( backgroundColor: context.theme.colors.primary, body: SafeArea( diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_signup_layout.dart b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_signup_layout.dart index 925e3a2a4..6bce2b9c8 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_signup_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_signup_layout.dart @@ -1,6 +1,8 @@ import 'package:deriv_auth/deriv_auth.dart'; import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; +import 'package:deriv_auth_ui/src/core/helpers/semantic_labels.dart'; +import 'package:deriv_auth_ui/src/core/states/auth_state_listener.dart'; import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_divider.dart'; import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_panel.dart'; import 'package:deriv_theme/deriv_theme.dart'; @@ -8,6 +10,8 @@ import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:deriv_auth_ui/src/core/states/auth_error_state_handler.dart'; + /// It offers creating demo accounts via email and third-party providers. /// It Also provides optional referral code section which can be disabled /// by setting [enableReferralSection] to false. @@ -21,7 +25,10 @@ class DerivSignupLayout extends StatefulWidget { required this.onLoginTapped, required this.signupPageLabel, required this.signupPageDescription, + this.isSocialAuthEnabled = true, + this.authErrorStateHandler, this.enableReferralSection = true, + this.onAuthError, Key? key, }) : super(key: key); @@ -31,6 +38,13 @@ class DerivSignupLayout extends StatefulWidget { /// Callback to be called when signup error occurs. final Function(DerivSignupErrorState) onSingupError; + /// Extension of base [AuthErrorStateHandler]. If not provided, base implementation will be used. + final AuthErrorStateHandler? authErrorStateHandler; + + /// Callback to be called on [DerivAuthErrorState]. + /// Useful if needed to do anything additional to [authErrorStateHandler]. + final Function(DerivAuthErrorState)? onAuthError; + /// Callback to be called when signup email is sent. final Function(String) onSingupEmailSent; @@ -49,6 +63,9 @@ class DerivSignupLayout extends StatefulWidget { /// Description of signup page. final String signupPageDescription; + /// Whether to display social auth buttons. + final bool isSocialAuthEnabled; + @override State createState() => _DerivSignupLayoutState(); } @@ -77,39 +94,47 @@ class _DerivSignupLayoutState extends State { Text(context.localization.labelSignUp, style: TextStyles.title), backgroundColor: context.theme.colors.secondary, ), - body: BlocConsumer( - listener: _onSignUpState, - builder: (BuildContext context, DerivSignupState state) => Form( - key: formKey, - child: SingleChildScrollView( - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: ThemeProvider.margin16, - vertical: ThemeProvider.margin24, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ..._buildHeaderSection(), - const SizedBox(height: ThemeProvider.margin24), - _buildEmailTextField(), - const SizedBox(height: ThemeProvider.margin36), - if (widget.enableReferralSection) _buildReferralSection(), - const SizedBox(height: ThemeProvider.margin16), - _buildSignUpButton(), - const SizedBox(height: ThemeProvider.margin24), - DerivSocialAuthDivider( - label: context.localization.labelOrSignUpWith, - ), - const SizedBox(height: ThemeProvider.margin24), - DerivSocialAuthPanel( - isEnabled: !isReferralEnabled, - onSocialAuthButtonPressed: - widget.onSocialAuthButtonPressed, - ), - const SizedBox(height: ThemeProvider.margin24), - _buildFooterSection(), - ], + body: DerivAuthStateListener( + authErrorStateHandler: widget.authErrorStateHandler, + onError: widget.onAuthError, + child: BlocConsumer( + listener: _onSignUpState, + builder: (BuildContext context, DerivSignupState state) => Form( + key: formKey, + child: SingleChildScrollView( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + vertical: ThemeProvider.margin24, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ..._buildHeaderSection(), + const SizedBox(height: ThemeProvider.margin24), + _buildEmailTextField(), + const SizedBox(height: ThemeProvider.margin36), + if (widget.enableReferralSection) _buildReferralSection(), + const SizedBox(height: ThemeProvider.margin16), + _buildSignUpButton(), + const SizedBox(height: ThemeProvider.margin24), + DerivSocialAuthDivider( + label: context.localization.labelOrSignUpWith, + isVisible: widget.isSocialAuthEnabled, + ), + if (widget.isSocialAuthEnabled) + const SizedBox(height: ThemeProvider.margin24), + DerivSocialAuthPanel( + isEnabled: !isReferralEnabled, + onSocialAuthButtonPressed: + widget.onSocialAuthButtonPressed, + isVisible: widget.isSocialAuthEnabled, + ), + if (widget.isSocialAuthEnabled) + const SizedBox(height: ThemeProvider.margin24), + _buildFooterSection(), + ], + ), ), ), ), @@ -136,6 +161,7 @@ class _DerivSignupLayoutState extends State { dialogTitle: context.localization.labelReferralInfoTitle, dialogDescription: context.localization.infoReferralInfoDescription, + positiveActionLabel: context.localization.actionOk, iconSize: ThemeProvider.iconSize24, ), const SizedBox(width: ThemeProvider.margin08), @@ -187,6 +213,8 @@ class _DerivSignupLayoutState extends State { vertical: ThemeProvider.margin16, ), child: BaseTextField( + semanticLabel: + SemanticsLabels.signupReferralTextFieldSemantic, controller: referralController, onChanged: (_) { if (mounted) { @@ -217,7 +245,7 @@ class _DerivSignupLayoutState extends State { widget.signupPageDescription, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ]; @@ -230,7 +258,7 @@ class _DerivSignupLayoutState extends State { context.localization.labelHaveAccount, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), InkWell( @@ -251,6 +279,7 @@ class _DerivSignupLayoutState extends State { ); Widget _buildEmailTextField() => BaseTextField( + semanticLabel: SemanticsLabels.signupEmailFieldSemantic, controller: emailController, focusNode: emailFocusNode, labelText: context.localization.labelEmail, @@ -266,25 +295,28 @@ class _DerivSignupLayoutState extends State { }, ); - Widget _buildSignUpButton() => PrimaryButton( - isEnabled: _isFormValid(), - onPressed: _onSignupTapped, - child: Center( - child: - context.read().state is DerivSignupProgressState - ? const LoadingIndicator( - valueColor: Colors.white, - strokeWidth: ThemeProvider.margin02, - height: ThemeProvider.iconSize16, - width: ThemeProvider.iconSize16, - ) - : Text( - context.localization.actionCreateAccount, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent, - ), + Widget _buildSignUpButton() => Semantics( + label: SemanticsLabels.signupButtonSemantic, + child: PrimaryButton( + isEnabled: _isFormValid(), + onPressed: _onSignupTapped, + child: Center( + child: context.read().state + is DerivSignupProgressState + ? const LoadingIndicator( + valueColor: Colors.white, + strokeWidth: ThemeProvider.margin02, + height: ThemeProvider.iconSize16, + width: ThemeProvider.iconSize16, + ) + : Text( + context.localization.actionCreateAccount, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, ), + ), + ), ), ); diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verification_done_layout.dart b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verification_done_layout.dart index 4df6eb34b..400d1e1f4 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verification_done_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verification_done_layout.dart @@ -64,7 +64,7 @@ class DerivVerificationDoneLayout extends StatelessWidget { context.localization.informLetsContinue, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ], diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verify_email_layout.dart b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verify_email_layout.dart index 46a525d8b..637f61365 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verify_email_layout.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verify_email_layout.dart @@ -64,7 +64,7 @@ class DerivVerifyEmailLayout extends StatelessWidget { context.localization.informVerificationEmailSent(email!), style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), textAlign: TextAlign.center, ), diff --git a/packages/deriv_auth_ui/lib/src/features/signup/models/deriv_password_policy_model.dart b/packages/deriv_auth_ui/lib/src/features/signup/models/deriv_password_policy_model.dart index cf5f0ef13..9edfec3ba 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/models/deriv_password_policy_model.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/models/deriv_password_policy_model.dart @@ -1,4 +1,3 @@ - /// Holds required information of a password policy. class DerivPasswordPolicyModel { /// Initializes [DerivPasswordPolicyModel]. diff --git a/packages/deriv_auth_ui/lib/src/features/signup/widgets/country_selection_list_widget.dart b/packages/deriv_auth_ui/lib/src/features/signup/widgets/country_selection_list_widget.dart index 0e4fa9133..294aa9aad 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/widgets/country_selection_list_widget.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/widgets/country_selection_list_widget.dart @@ -113,7 +113,7 @@ class _CountrySelectionListWidgetState focusNode: _searchFocusNode, style: context.theme.textStyle( textStyle: TextStyles.subheading, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), decoration: InputDecoration( border: InputBorder.none, @@ -145,7 +145,7 @@ class _CountrySelectionListWidgetState _filteredCountries[index].name, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), diff --git a/packages/deriv_auth_ui/lib/src/features/signup/widgets/password_policy_checker_widget.dart b/packages/deriv_auth_ui/lib/src/features/signup/widgets/password_policy_checker_widget.dart index 68dc90052..508f8eb8b 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/widgets/password_policy_checker_widget.dart +++ b/packages/deriv_auth_ui/lib/src/features/signup/widgets/password_policy_checker_widget.dart @@ -69,7 +69,7 @@ class PasswordPolicyCheckerWidget extends StatelessWidget { context.localization.informPasswordPolicy, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), const SizedBox(height: ThemeProvider.margin04), @@ -114,7 +114,7 @@ class PasswordPolicyCheckerWidget extends StatelessWidget { textStyle: TextStyles.body1, color: policy.isMatchWith(password) ? context.theme.colors.hover - : context.theme.colors.lessProminent, + : context.theme.colors.general, ); Widget _buildPolicyIcon({ @@ -129,7 +129,7 @@ class PasswordPolicyCheckerWidget extends StatelessWidget { policy.isMatchWith(password) ? Icons.check : Icons.circle; final Color color = policy.isMatchWith(password) - ? context.theme.colors.lessProminent + ? context.theme.colors.general : context.theme.colors.coral; return Padding( diff --git a/packages/deriv_auth_ui/pubspec.yaml b/packages/deriv_auth_ui/pubspec.yaml index 0b7c361f6..b90543d4c 100644 --- a/packages/deriv_auth_ui/pubspec.yaml +++ b/packages/deriv_auth_ui/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_auth_ui description: A flutter package for deriv auth UI flows. -version: 0.0.1 +version: 0.0.1+3 environment: sdk: ">=3.0.0 <4.0.0" @@ -18,20 +18,23 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_auth - ref: d5bef178341a318f911b42b8105d8c3d50c6b63e + ref: deriv_auth-v1.1.0 deriv_theme: - path: ../deriv_theme - # git: - # url: git@github.com:regentmarkets/flutter-deriv-packages.git - # path: packages/deriv_theme - # ref: dev + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_theme + ref: deriv_theme-v2.1.0 deriv_ui: - path: ../deriv_ui + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_ui + ref: deriv_ui-v0.0.1+2 flutter_svg: ^1.1.6 smooth_page_indicator: ^1.1.0 intl_utils: ^2.8.3 intl: ^0.18.0 equatable: ^2.0.5 + package_info_plus: ^3.0.3 dev_dependencies: flutter_test: @@ -40,7 +43,7 @@ dev_dependencies: sdk: flutter flutter_lints: ^2.0.0 mocktail: ^0.3.0 - patrol: ^1.1.4 + patrol_finders: ^1.0.0 bloc_test: ^9.1.3 flutter_intl: diff --git a/packages/deriv_auth_ui/test/core/layouts/deriv_unavailable_country_layout_test.dart b/packages/deriv_auth_ui/test/core/layouts/deriv_unavailable_country_layout_test.dart index ed7d7feda..667fc983e 100644 --- a/packages/deriv_auth_ui/test/core/layouts/deriv_unavailable_country_layout_test.dart +++ b/packages/deriv_auth_ui/test/core/layouts/deriv_unavailable_country_layout_test.dart @@ -1,12 +1,11 @@ import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:deriv_ui/presentation/widgets/fullscreen_message.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../base_test_app.dart'; void main() { - patrolTest('DerivUnavailableCountryLayout', (PatrolTester $) async { + patrolWidgetTest('DerivUnavailableCountryLayout', (PatrolTester $) async { await $.pumpWidgetAndSettle(BaseTestApp( child: DerivUnavailableCountryLayout( userAgent: '', diff --git a/packages/deriv_auth_ui/test/core/states/auth_error_state_mapper_test.dart b/packages/deriv_auth_ui/test/core/states/auth_error_state_mapper_test.dart new file mode 100644 index 000000000..12d6a5f74 --- /dev/null +++ b/packages/deriv_auth_ui/test/core/states/auth_error_state_mapper_test.dart @@ -0,0 +1,206 @@ +import 'package:flutter/material.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth_ui/deriv_auth_ui.dart'; + +class MockBuildContext extends Mock implements BuildContext {} + +// Create a mock implementation of AuthErrorStateHandler for testing. +final class MockAuthErrorStateHandler extends AuthErrorStateHandler { + MockAuthErrorStateHandler({required super.context}); + + DerivAuthErrorState? lastHandledError; + + @override + void onMissingOtp(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onSelfClosed(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onUnsupportedCountry(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onAccountUnavailable(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onInvalidCredentials(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onFailedAuthorization(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onInavlidResidence(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onExpiredAccount(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void invalid2faCode(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onConnectionError(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onSwitchAccountError(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } +} + +void main() { + // Create an instance of MockAuthErrorStateHandler for testing. + final MockAuthErrorStateHandler mockHandler = MockAuthErrorStateHandler( + context: MockBuildContext(), + ); + + test('authErrorStateMapper handles missing OTP', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.missingOtp, isSocialLogin: false, message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles self closed', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.selfClosed, isSocialLogin: false, message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles unsupported country', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.unsupportedCountry, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles account unavailable', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.accountUnavailable, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles invalid credentials', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.invalidCredential, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles failed authorization', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.failedAuthorization, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles invalid residence', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.invalidResidence, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles expired account', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.expiredAccount, isSocialLogin: false, message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles invalid 2FA code', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.invalid2faCode, isSocialLogin: false, message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles connection error', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.connectionError, isSocialLogin: false, message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles switch account error', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.switchAccountError, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + expect(mockHandler.lastHandledError, equals(errorState)); + }); +} diff --git a/packages/deriv_auth_ui/test/core/states/auth_state_listener_test.dart b/packages/deriv_auth_ui/test/core/states/auth_state_listener_test.dart new file mode 100644 index 000000000..85db400ae --- /dev/null +++ b/packages/deriv_auth_ui/test/core/states/auth_state_listener_test.dart @@ -0,0 +1,134 @@ +import 'package:deriv_auth/core/models/landig_comany_model.dart'; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth_ui/src/core/states/auth_state_listener.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:patrol_finders/patrol_finders.dart'; + +import '../../pump_app.dart'; + +class MockDerivAuthCubit extends Mock implements DerivAuthCubit {} + +class MockBuildContext extends Mock implements BuildContext {} + +void main() { + late DerivAuthCubit authCubit; + + setUpAll(() { + authCubit = MockDerivAuthCubit(); + + when(() => authCubit.close()).thenAnswer((_) async {}); + }); + + group('AuthStateLister', () { + patrolWidgetTest('onLoggedIn is called based on logged in state', + (PatrolTester $) async { + bool isOnLoggedInCalled = false; + + final DerivAuthLoggedInState mockAuthState = + DerivAuthLoggedInState(const DerivAuthModel( + authorizeEntity: AuthorizeEntity(), + landingCompany: LandingCompanyEntity(), + )); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + BlocProvider( + create: (_) => authCubit, + child: DerivAuthStateListener( + onLoggedIn: (_) { + isOnLoggedInCalled = true; + }, + child: Container()), + ), + ); + + expect(isOnLoggedInCalled, true); + }); + + patrolWidgetTest('onLoggedOut is called based on logged out state', + (PatrolTester $) async { + bool isOnLoggedOutCalled = false; + + final DerivAuthLoggedOutState mockAuthState = DerivAuthLoggedOutState(); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + BlocProvider( + create: (_) => authCubit, + child: DerivAuthStateListener( + onLoggedOut: () { + isOnLoggedOutCalled = true; + }, + child: Container()), + ), + ); + + expect(isOnLoggedOutCalled, true); + }); + + patrolWidgetTest('onLoading is called based on loading state', + (PatrolTester $) async { + bool isOnLoadingCalled = false; + + final DerivAuthLoadingState mockAuthState = DerivAuthLoadingState(); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + BlocProvider( + create: (_) => authCubit, + child: DerivAuthStateListener( + onLoading: () { + isOnLoadingCalled = true; + }, + child: Container()), + ), + ); + + expect(isOnLoadingCalled, true); + }); + + patrolWidgetTest('onError is called based on error state', + (PatrolTester $) async { + bool isOnErrorCalled = false; + + final DerivAuthErrorState mockAuthState = DerivAuthErrorState( + type: AuthErrorType.accountUnavailable, + message: 'error', + isSocialLogin: false, + ); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + BlocProvider( + create: (_) => authCubit, + child: DerivAuthStateListener( + onError: (_) { + isOnErrorCalled = true; + }, + child: Container()), + ), + ); + + expect(isOnErrorCalled, true); + }); + }); +} diff --git a/packages/deriv_auth_ui/test/features/get_started/layouts/deriv_get_started_layout_test.dart b/packages/deriv_auth_ui/test/features/get_started/layouts/deriv_get_started_layout_test.dart index 82100761b..a59eb0741 100644 --- a/packages/deriv_auth_ui/test/features/get_started/layouts/deriv_get_started_layout_test.dart +++ b/packages/deriv_auth_ui/test/features/get_started/layouts/deriv_get_started_layout_test.dart @@ -5,8 +5,7 @@ import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../pump_app.dart'; class MockDerivGetStartedSlideModel extends Mock @@ -26,8 +25,10 @@ void main() { when(() => mockSlideModel.supportingText).thenReturn('Supporting text'); }); - patrolTest('should render DerivGetStartedLayout', (PatrolTester $) async { + patrolWidgetTest('should render DerivGetStartedLayout', + (PatrolTester $) async { await $.pumpApp(DerivGetStartedLayout( + onTapNavigation: () {}, slides: [mockSlideModel], appLogoIconPath: appLogoIconPath, backgroundImagePath: backgroundImagePath, @@ -41,11 +42,12 @@ void main() { expect($(SecondaryButton), findsOneWidget); }); - patrolTest('should call onLoginTapped when login button is pressed', + patrolWidgetTest('should call onLoginTapped when login button is pressed', (PatrolTester $) async { bool loginTapped = false; await $.pumpApp(DerivGetStartedLayout( + onTapNavigation: () {}, slides: [mockSlideModel], appLogoIconPath: appLogoIconPath, backgroundImagePath: backgroundImagePath, @@ -60,11 +62,12 @@ void main() { expect(loginTapped, isTrue); }); - patrolTest('should call onSignupTapped when signup button is pressed', + patrolWidgetTest('should call onSignupTapped when signup button is pressed', (PatrolTester $) async { bool signupTapped = false; await $.pumpApp(DerivGetStartedLayout( + onTapNavigation: () {}, slides: [mockSlideModel], appLogoIconPath: appLogoIconPath, backgroundImagePath: backgroundImagePath, diff --git a/packages/deriv_auth_ui/test/features/login/layouts/deriv_2fa_layout_test.dart b/packages/deriv_auth_ui/test/features/login/layouts/deriv_2fa_layout_test.dart index a835bd124..5907efae0 100644 --- a/packages/deriv_auth_ui/test/features/login/layouts/deriv_2fa_layout_test.dart +++ b/packages/deriv_auth_ui/test/features/login/layouts/deriv_2fa_layout_test.dart @@ -8,8 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../mocks.dart'; import '../../../pump_app.dart'; @@ -25,7 +24,7 @@ void main() { mockPassword = 'test1234'; }); - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { when(() => authCubit.state).thenAnswer((_) => DerivAuthLoggedOutState()); when(() => authCubit.stream) @@ -44,7 +43,8 @@ void main() { expect($(ElevatedButton).$('Proceed'), findsOneWidget); }); - patrolTest('proceeds to login on correct code', (PatrolTester $) async { + patrolWidgetTest('proceeds to login on correct code', + (PatrolTester $) async { when(() => authCubit.state).thenAnswer((_) => DerivAuthLoggedOutState()); when(() => authCubit.stream) @@ -54,10 +54,10 @@ void main() { email: any(named: 'email'), password: any(named: 'password'), otp: any(named: 'otp'))) - .thenAnswer((_) async => DerivAuthLoggedInState( - authorizeEntity: const AuthorizeEntity(), - landingCompany: const LandingCompanyEntity(), - )); + .thenAnswer((_) async => DerivAuthLoggedInState(const DerivAuthModel( + authorizeEntity: AuthorizeEntity(), + landingCompany: LandingCompanyEntity(), + ))); await $.pumpApp( BlocProvider.value( diff --git a/packages/deriv_auth_ui/test/features/login/layouts/deriv_login_layout_test.dart b/packages/deriv_auth_ui/test/features/login/layouts/deriv_login_layout_test.dart index 101e4be1e..83e94330e 100644 --- a/packages/deriv_auth_ui/test/features/login/layouts/deriv_login_layout_test.dart +++ b/packages/deriv_auth_ui/test/features/login/layouts/deriv_login_layout_test.dart @@ -9,8 +9,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../mocks.dart'; import '../../../pump_app.dart'; @@ -23,7 +22,7 @@ void main() { setUpAll(() => authCubit = MockAuthCubit()); - patrolTest( + patrolWidgetTest( 'renders email and password field including social auth buttons.', (PatrolTester $) async { final mockAuthState = DerivAuthLoggedOutState(); @@ -41,9 +40,9 @@ void main() { greetingLabel: greetingLabel, onResetPassTapped: () {}, onSignupTapped: () {}, - onLoginError: (_) {}, onLoggedIn: (_) {}, onSocialAuthButtonPressed: (p0) {}, + onLoginError: (_) {}, ), ), ); @@ -54,7 +53,7 @@ void main() { expect($(DerivSocialAuthPanel), findsOneWidget); }); - patrolTest('displays invalid email error on invalid email typed.', + patrolWidgetTest('displays invalid email error on invalid email typed.', (PatrolTester $) async { final mockAuthState = DerivAuthLoggedOutState(); const invalidEmail = 'invalid-email'; @@ -72,9 +71,9 @@ void main() { greetingLabel: greetingLabel, onResetPassTapped: () {}, onSignupTapped: () {}, - onLoginError: (_) {}, onLoggedIn: (_) {}, onSocialAuthButtonPressed: (_) {}, + onLoginError: (_) {}, ), ), ); @@ -87,7 +86,7 @@ void main() { expect($(Text).$('Enter a valid email address'), findsOneWidget); }); - patrolTest('displays loading error on AuthLoadingState', + patrolWidgetTest('displays loading error on AuthLoadingState', (PatrolTester $) async { final mockAuthState = DerivAuthLoadingState(); @@ -105,16 +104,16 @@ void main() { greetingLabel: greetingLabel, onResetPassTapped: () {}, onSignupTapped: () {}, - onLoginError: (_) {}, onLoggedIn: (_) {}, onSocialAuthButtonPressed: (_) {}, + onLoginError: (_) {}, ), )); expect($(LoadingIndicator), findsOneWidget); }); - patrolTest('calls signupTapped when signup button is pressed.', + patrolWidgetTest('calls signupTapped when signup button is pressed.', (PatrolTester $) async { final mockAuthState = DerivAuthLoggedOutState(); @@ -134,9 +133,9 @@ void main() { onSignupTapped: () { onSignupTappedCalled = true; }, - onLoginError: (_) {}, onLoggedIn: (_) {}, onSocialAuthButtonPressed: (_) {}, + onLoginError: (_) {}, ), )); @@ -149,10 +148,13 @@ void main() { expect(onSignupTappedCalled, isTrue); }); - patrolTest('calls onLoggedIn on successful login.', (PatrolTester $) async { + patrolWidgetTest('calls onLoggedIn on successful login.', + (PatrolTester $) async { final mockAuthState = DerivAuthLoggedInState( - authorizeEntity: const AuthorizeEntity(), - landingCompany: const LandingCompanyEntity(), + const DerivAuthModel( + authorizeEntity: AuthorizeEntity(), + landingCompany: LandingCompanyEntity(), + ), ); when(() => authCubit.state).thenAnswer((_) => mockAuthState); @@ -169,20 +171,23 @@ void main() { greetingLabel: greetingLabel, onResetPassTapped: () {}, onSignupTapped: () {}, - onLoginError: (_) {}, onLoggedIn: (_) { onLoggedInCalled = true; }, onSocialAuthButtonPressed: (_) {}, + onLoginError: (_) {}, ), )); expect(onLoggedInCalled, isTrue); }); - patrolTest('calls onLoginError on login error.', (PatrolTester $) async { + patrolWidgetTest('calls onLoginError on login error.', + (PatrolTester $) async { final mockAuthState = DerivAuthErrorState( - message: 'error', type: AuthErrorType.failedAuthorization); + isSocialLogin: false, + message: 'error', + type: AuthErrorType.failedAuthorization); when(() => authCubit.state).thenAnswer((_) => mockAuthState); @@ -209,7 +214,36 @@ void main() { expect(onLoginErrorCalled, isTrue); }); - patrolTest('calls resetPassTapped when reset button is pressed.', + patrolWidgetTest('calls [AuthErrorStateHandler] on auth error state.', + (PatrolTester $) async { + final mockAuthState = DerivAuthErrorState( + isSocialLogin: false, + message: 'error', + type: AuthErrorType.failedAuthorization, + ); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream) + .thenAnswer((_) => Stream.fromIterable([mockAuthState])); + + await $.pumpApp(BlocProvider.value( + value: authCubit, + child: DerivLoginLayout( + welcomeLabel: welcomeLabel, + greetingLabel: greetingLabel, + onResetPassTapped: () {}, + onSignupTapped: () {}, + onLoginError: (_) {}, + onLoggedIn: (_) {}, + onSocialAuthButtonPressed: (_) {}, + ), + )); + + expect($(PopupAlertDialog).$('Authorization failed.'), findsOneWidget); + }); + + patrolWidgetTest('calls resetPassTapped when reset button is pressed.', (PatrolTester $) async { final mockAuthState = DerivAuthLoggedOutState(); @@ -229,9 +263,9 @@ void main() { onResetPassTappedCalled = true; }, onSignupTapped: () {}, - onLoginError: (_) {}, onLoggedIn: (_) {}, onSocialAuthButtonPressed: (_) {}, + onLoginError: (_) {}, ), )); @@ -240,7 +274,7 @@ void main() { expect(onResetPassTappedCalled, isTrue); }); - patrolTest( + patrolWidgetTest( 'calls onSocialAuthButtonPressed when social auth button is pressed.', (PatrolTester $) async { final mockAuthState = DerivAuthLoggedOutState(); @@ -259,11 +293,11 @@ void main() { greetingLabel: greetingLabel, onResetPassTapped: () {}, onSignupTapped: () {}, - onLoginError: (_) {}, onLoggedIn: (_) {}, onSocialAuthButtonPressed: (_) { onSocialAuthButtonPressedCalled = true; }, + onLoginError: (_) {}, ), )); diff --git a/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_choose_new_pass_layout_test.dart b/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_choose_new_pass_layout_test.dart index b351286c8..895f3addb 100644 --- a/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_choose_new_pass_layout_test.dart +++ b/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_choose_new_pass_layout_test.dart @@ -7,8 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../mocks.dart'; import '../../../pump_app.dart'; @@ -21,7 +20,7 @@ void main() { mockResetPassCubit = MockDerivResetPassCubit(); }); - patrolTest('should render DerivChooseNewPassLayout', + patrolWidgetTest('should render DerivChooseNewPassLayout', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); @@ -43,7 +42,7 @@ void main() { expect($(DerivChooseNewPassLayout), findsOneWidget); }); - patrolTest( + patrolWidgetTest( 'should call onResetPassError when state is DerivResetPassErrorState', (PatrolTester $) async { const errorState = DerivResetPassErrorState(errorMessage: 'test error'); @@ -70,7 +69,7 @@ void main() { expect(onResetPassErrorCalled, isTrue); }); - patrolTest( + patrolWidgetTest( 'should call onResetPassSucceed after 2 seconds when state is DerivResetPassPasswordChangedState', (PatrolTester $) async { const passwordChangedState = DerivResetPassPasswordChangedState(); @@ -101,7 +100,7 @@ void main() { expect(onResetPassSucceedCalled, isTrue); }); - patrolTest('should call changePassword when input is valid', + patrolWidgetTest('should call changePassword when input is valid', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); const newPassTest = 'newPassWordTest123@'; @@ -131,7 +130,7 @@ void main() { token: token, newPassword: newPassTest)).called(1); }); - patrolTest('should not call changePassword when input is invalid', + patrolWidgetTest('should not call changePassword when input is invalid', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); const newPassTest = 'pass'; diff --git a/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_reset_pass_layout_test.dart b/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_reset_pass_layout_test.dart index 5f1df8405..743591f1e 100644 --- a/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_reset_pass_layout_test.dart +++ b/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_reset_pass_layout_test.dart @@ -7,8 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../mocks.dart'; import '../../../pump_app.dart'; @@ -20,7 +19,8 @@ void main() { mockResetPassCubit = MockDerivResetPassCubit(); }); - patrolTest('should render DerivResetPassLayout', (PatrolTester $) async { + patrolWidgetTest('should render DerivResetPassLayout', + (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); when(() => mockResetPassCubit.state).thenAnswer((_) => resetPassState); @@ -39,7 +39,7 @@ void main() { expect($(DerivResetPassLayout), findsOneWidget); }); - patrolTest( + patrolWidgetTest( 'should show emailSentPage when state is DerivResetPassEmailSentState', (PatrolTester $) async { const resetPassState = DerivResetPassEmailSentState(); @@ -63,7 +63,7 @@ void main() { findsOneWidget); }); - patrolTest( + patrolWidgetTest( 'should show submitEmailForm when state is DerivResetPassInitialState', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); @@ -87,7 +87,8 @@ void main() { findsOneWidget); }); - patrolTest('should not call sendVerificationEmail when email is invalid', + patrolWidgetTest( + 'should not call sendVerificationEmail when email is invalid', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); const email = 'wrongEmail@format'; @@ -115,7 +116,7 @@ void main() { verifyNever(() => mockResetPassCubit.sendVerificationEmail(email)); }); - patrolTest('should call sendVerificationEmail when email is valid', + patrolWidgetTest('should call sendVerificationEmail when email is valid', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); const email = 'correctEmail@format.com'; @@ -145,7 +146,7 @@ void main() { verify(() => mockResetPassCubit.sendVerificationEmail(email)).called(1); }); - patrolTest( + patrolWidgetTest( 'should call OnResetPassError when state is DerivResetPassErrorState', (PatrolTester $) async { const errorState = DerivResetPassErrorState(errorMessage: 'test error'); diff --git a/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_success_pass_change_layout_test.dart b/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_success_pass_change_layout_test.dart new file mode 100644 index 000000000..9e647d321 --- /dev/null +++ b/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_success_pass_change_layout_test.dart @@ -0,0 +1,17 @@ +import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:patrol_finders/patrol_finders.dart'; + +import '../../../pump_app.dart'; + +void main() { + group('DerivSuccessPassChangeLayout', () { + patrolWidgetTest('renders correctly', (PatrolTester $) async { + await $.pumpApp(settle: false, const DerivSuccessPassChangeLayout()); + + expect($(Text).$('Reset Password'), findsOneWidget); + expect($(Text).$('Your password has been reset'), findsOneWidget); + }); + }); +} diff --git a/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_cubit_test.dart b/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_cubit_test.dart index 7d854c8bc..ea764dc15 100644 --- a/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_cubit_test.dart +++ b/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_cubit_test.dart @@ -6,29 +6,95 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('DerivCountrySelectionCubit', () { - late DerivCountrySelectionCubit cubit; + late DerivCountrySelectionCubit countrySelectionCubit; + late List countriesList; setUp(() { - final countries = [ + countriesList = [ const DerivResidenceModel( code: 'us', name: 'United States', isDisabled: false), + const DerivResidenceModel( + code: 'br', name: 'Brazil', isDisabled: false), const DerivResidenceModel( code: 'fr', name: 'France', isDisabled: false), const DerivResidenceModel( code: 'de', name: 'Germany', isDisabled: false), ]; - cubit = DerivCountrySelectionCubit(Future.value(countries)); - }); - test('initial state is DerivCountrySelectionInitialState', () { - expect(cubit.state, const DerivCountrySelectionInitialState()); + countrySelectionCubit = + DerivCountrySelectionCubit(Future.value(countriesList)); }); blocTest( - 'emits DerivCountrySelectionLoadedState with filtered countries when fetchResidenceCounties is called', - build: () => cubit, - act: (cubit) => cubit.fetchResidenceCounties(), - expect: () => [isA()], - ); + 'Initial states is [DerivCountrySelectionInitialState]', + build: () => countrySelectionCubit, + verify: (DerivCountrySelectionCubit cubit) { + expect( + cubit.state, + isA(), + ); + }); + + blocTest( + 'Verify cubit emits the right states by loading country list', + build: () => countrySelectionCubit, + act: (DerivCountrySelectionCubit cubit) => + countrySelectionCubit.fetchResidenceCountries(), + verify: (DerivCountrySelectionCubit cubit) { + expect( + cubit.state, + isA(), + ); + }); + + blocTest( + 'Verify cubit emits the right states by changing the country selection.', + build: () => countrySelectionCubit, + seed: () => DerivCountrySelectionLoadedState(countriesList), + act: (DerivCountrySelectionCubit cubit) => cubit.changeSelectedCountry( + selectedCountry: countriesList[0], + ), + verify: (DerivCountrySelectionCubit cubit) { + expect( + countrySelectionCubit.state, + isA(), + ); + + expect( + countrySelectionCubit.state.selectedCountry?.code, + countriesList[0].code, + ); + }); + + blocTest( + 'Verify cubit emits the right states when it changing the consent status(Checkbox).', + build: () => countrySelectionCubit, + seed: () => DerivCountrySelectionLoadedState(countriesList), + act: (DerivCountrySelectionCubit cubit) { + cubit + ..changeSelectedCountry( + selectedCountry: const DerivResidenceModel( + code: 'br', name: 'Brazil', isDisabled: false), + ) + ..updateCountryConsentStatus( + agreedToTerms: !cubit.state.agreedToTerms, + ); + }, + verify: (DerivCountrySelectionCubit cubit) { + expect( + cubit.state, + isA(), + ); + + expect( + cubit.state.selectedCountryRequiresConsent, + true, + ); + + expect( + cubit.state.agreedToTerms, + true, + ); + }); }); } diff --git a/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_state_test.dart b/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_state_test.dart deleted file mode 100644 index 146c66014..000000000 --- a/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_state_test.dart +++ /dev/null @@ -1,39 +0,0 @@ -// ignore_for_file: always_specify_types - -import 'package:bloc_test/bloc_test.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('DerivCountrySelectionCubit', () { - late DerivCountrySelectionCubit cubit; - - setUp(() { - final countries = [ - const DerivResidenceModel( - code: 'us', name: 'United States', isDisabled: false), - const DerivResidenceModel( - code: 'fr', name: 'France', isDisabled: false), - const DerivResidenceModel( - code: 'de', name: 'Germany', isDisabled: false), - ]; - cubit = DerivCountrySelectionCubit(Future.value(countries)); - }); - - test('initial state is DerivCountrySelectionInitialState', () { - expect(cubit.state, const DerivCountrySelectionInitialState()); - }); - - blocTest( - 'emits DerivCountrySelectionLoadedState with filtered countries when fetchResidenceCounties is called', - build: () => cubit, - act: (cubit) => cubit.fetchResidenceCounties(), - expect: () => [ - const DerivCountrySelectionLoadedState([ - DerivResidenceModel( - code: 'us', name: 'United States', isDisabled: false), - ]), - ], - ); - }); -} diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_country_selection_layout_test.dart b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_country_selection_layout_test.dart index 6efff970b..9ded487c3 100644 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_country_selection_layout_test.dart +++ b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_country_selection_layout_test.dart @@ -2,8 +2,7 @@ import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../pump_app.dart'; void main() { @@ -13,13 +12,14 @@ void main() { setUp(() { residences = Future>.value([ - const DerivResidenceModel(code: 'ID', name: 'Indonesia', isDisabled: false), + const DerivResidenceModel( + code: 'ID', name: 'Indonesia', isDisabled: false), const DerivResidenceModel( code: 'UK', name: 'England', isDisabled: true), ]); }); - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp(DerivCountrySelectionLayout( onNextPressed: () {}, verificationCode: '123456', diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_email_not_received_layout_test.dart b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_email_not_received_layout_test.dart index 910b3b3fb..11cbcba05 100644 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_email_not_received_layout_test.dart +++ b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_email_not_received_layout_test.dart @@ -1,13 +1,12 @@ import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../pump_app.dart'; void main() { group('DerivEmailNotReceivedLayout', () { - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp(DerivEmailNotReceivedLayout( onReEnterEmailPressed: () {}, )); @@ -17,7 +16,8 @@ void main() { findsOneWidget); }); - patrolTest('onReEnterEmailPressed is called when tapped on the button', + patrolWidgetTest( + 'onReEnterEmailPressed is called when tapped on the button', (PatrolTester $) async { bool isReEnterEmailPressed = false; diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_set_password_layout_test.dart b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_set_password_layout_test.dart index 4e6ede2a4..28047562f 100644 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_set_password_layout_test.dart +++ b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_set_password_layout_test.dart @@ -7,8 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../mocks.dart'; import '../../../pump_app.dart'; @@ -31,7 +30,7 @@ void main() { }); group('DerivSetPasswordLayout', () { - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp( settle: false, MultiBlocProvider( @@ -40,7 +39,6 @@ void main() { BlocProvider.value(value: signupCubit), ], child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) {}, onDerivSignupState: (_, __) {}, onPreviousPressed: () {}, verificationCode: '123456', @@ -53,32 +51,8 @@ void main() { expect($(ElevatedButton), findsNWidgets(2)); }); - patrolTest('onDerivAuthState is called on DerivAuth state changes', - (PatrolTester $) async { - bool isOnDerivAuthStateCalled = false; - - when(() => authCubit.stream).thenAnswer((_) => Stream.fromIterable([ - DerivAuthLoadingState(), - DerivAuthLoggedOutState(), - ])); - - await $.pumpApp(MultiBlocProvider( - providers: [ - BlocProvider.value(value: authCubit), - BlocProvider.value(value: signupCubit), - ], - child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) => isOnDerivAuthStateCalled = true, - onDerivSignupState: (_, __) {}, - onPreviousPressed: () {}, - verificationCode: 'verificationCode', - residence: 'residence'), - )); - - expect(isOnDerivAuthStateCalled, true); - }); - - patrolTest('onDerivSignupState is called on DerivSignup state changes', + patrolWidgetTest( + 'onDerivSignupState is called on DerivSignup state changes', (PatrolTester $) async { bool isOnDerivSignupStateCalled = false; @@ -95,7 +69,6 @@ void main() { BlocProvider.value(value: signupCubit), ], child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) {}, onDerivSignupState: (_, __) => isOnDerivSignupStateCalled = true, onPreviousPressed: () {}, @@ -106,7 +79,66 @@ void main() { expect(isOnDerivSignupStateCalled, true); }); - patrolTest('onPreviousPressed is called upon tapping previous button', + patrolWidgetTest('calls [AuthErrorStateHandler] on auth error state.', + (PatrolTester $) async { + final mockAuthState = DerivAuthErrorState( + isSocialLogin: false, + message: 'error', + type: AuthErrorType.failedAuthorization, + ); + + when(() => authCubit.state).thenReturn(mockAuthState); + when(() => authCubit.stream) + .thenAnswer((_) => Stream.fromIterable([mockAuthState])); + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + ], + child: DerivSetPasswordLayout( + onDerivSignupState: (_, __) {}, + onPreviousPressed: () {}, + verificationCode: 'verificationCode', + residence: 'residence'), + )); + + expect($(PopupAlertDialog).$('Authorization failed.'), findsOneWidget); + }); + + patrolWidgetTest('onAuthError is called on auth error state.', + (PatrolTester $) async { + final mockAuthState = DerivAuthErrorState( + isSocialLogin: false, + message: 'error', + type: AuthErrorType.failedAuthorization, + ); + + when(() => authCubit.state).thenReturn(mockAuthState); + when(() => authCubit.stream) + .thenAnswer((_) => Stream.fromIterable([mockAuthState])); + + bool isOnAuthErrorCalled = false; + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + ], + child: DerivSetPasswordLayout( + onDerivSignupState: (_, __) {}, + onPreviousPressed: () {}, + onAuthError: (_) { + isOnAuthErrorCalled = true; + }, + verificationCode: 'verificationCode', + residence: 'residence'), + )); + + expect(isOnAuthErrorCalled, true); + }); + + patrolWidgetTest('onPreviousPressed is called upon tapping previous button', (PatrolTester $) async { bool isOnPreviousPressedCalled = false; @@ -118,7 +150,6 @@ void main() { BlocProvider.value(value: signupCubit), ], child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) {}, onDerivSignupState: (_, __) {}, onPreviousPressed: () => isOnPreviousPressedCalled = true, verificationCode: 'verificationCode', @@ -130,7 +161,8 @@ void main() { expect(isOnPreviousPressedCalled, true); }); - patrolTest('password is no longer obscure after visibility icon pressed', + patrolWidgetTest( + 'password is no longer obscure after visibility icon pressed', (PatrolTester $) async { await $.pumpApp( settle: false, @@ -140,7 +172,6 @@ void main() { BlocProvider.value(value: signupCubit), ], child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) {}, onDerivSignupState: (_, __) {}, onPreviousPressed: () {}, verificationCode: 'verificationCode', @@ -158,7 +189,7 @@ void main() { findsOneWidget); }); - patrolTest('start trading button is disabled until password is valid', + patrolWidgetTest('start trading button is disabled until password is valid', (PatrolTester $) async { const validPassword = 'Abcdefg123'; @@ -170,7 +201,6 @@ void main() { BlocProvider.value(value: signupCubit), ], child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) {}, onDerivSignupState: (_, __) {}, onPreviousPressed: () {}, verificationCode: 'verificationCode', diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_signup_layout_test.dart b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_signup_layout_test.dart index c3186feb6..820830810 100644 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_signup_layout_test.dart +++ b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_signup_layout_test.dart @@ -8,41 +8,50 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../mocks.dart'; import '../../../pump_app.dart'; void main() { group('DerivSignupLayout', () { late MockSignupCubit signupCubit; + late MockAuthCubit authCubit; const String signupPageLabel = 'Create a free account'; const String signupPageDescription = 'Start trading within minutes.'; setUpAll(() { signupCubit = MockSignupCubit(); + authCubit = MockAuthCubit(); when(() => signupCubit.state) .thenAnswer((_) => const DerivSignupInitialState()); when(() => signupCubit.stream).thenAnswer( (_) => Stream.fromIterable([const DerivSignupInitialState()])); + + when(() => authCubit.state).thenAnswer((_) => DerivAuthLoadingState()); + + when(() => authCubit.stream) + .thenAnswer((_) => Stream.fromIterable([DerivAuthLoadingState()])); }); - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp( settle: false, - BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) {}, - onSingupError: (_) {}, - onSingupEmailSent: (_) {}, - onSignupPressed: () {}, - onLoginTapped: () {}, + BlocProvider.value( + value: authCubit, + child: BlocProvider.value( + value: signupCubit, + child: DerivSignupLayout( + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onLoginTapped: () {}, + ), ), )); @@ -52,23 +61,26 @@ void main() { expect($(DerivSocialAuthPanel), findsOneWidget); }); - patrolTest( + patrolWidgetTest( 'onSocialAuthButtonPressed is called upon tapping social auth option', (PatrolTester $) async { bool isOnSocialAuthButtonPressedCalled = false; - await $.pumpApp(BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) { - isOnSocialAuthButtonPressedCalled = true; - }, - onSingupError: (_) {}, - onSingupEmailSent: (_) {}, - onSignupPressed: () {}, - onLoginTapped: () {}, + await $.pumpApp(BlocProvider.value( + value: authCubit, + child: BlocProvider.value( + value: signupCubit, + child: DerivSignupLayout( + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) { + isOnSocialAuthButtonPressedCalled = true; + }, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onLoginTapped: () {}, + ), ), )); @@ -77,7 +89,7 @@ void main() { expect(isOnSocialAuthButtonPressedCalled, true); }); - patrolTest('onSignupEmailSent is called upon sign up email sent', + patrolWidgetTest('onSignupEmailSent is called upon sign up email sent', (PatrolTester $) async { bool isOnSignupEmailSentCalled = false; @@ -87,39 +99,43 @@ void main() { when(() => signupCubit.stream).thenAnswer( (_) => Stream.fromIterable([const DerivSignupEmailSentState()])); - await $.pumpApp(BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) {}, - onSingupError: (_) {}, - onSingupEmailSent: (_) => isOnSignupEmailSentCalled = true, - onSignupPressed: () {}, - onLoginTapped: () {}, + await $.pumpApp(BlocProvider.value( + value: authCubit, + child: BlocProvider.value( + value: signupCubit, + child: DerivSignupLayout( + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) => isOnSignupEmailSentCalled = true, + onSignupPressed: () {}, + onLoginTapped: () {}), ), )); expect(isOnSignupEmailSentCalled, true); }); - patrolTest('onSignupPressed is called upon tapping signup button', + patrolWidgetTest('onSignupPressed is called upon tapping signup button', (PatrolTester $) async { bool isOnSignupPressedCalled = false; when(() => signupCubit.sendVerificationEmail('test@gmail.com')) .thenAnswer((_) async => const DerivSignupEmailSentState()); - await $.pumpApp(BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) {}, - onSingupError: (_) {}, - onSingupEmailSent: (_) {}, - onSignupPressed: () => isOnSignupPressedCalled = true, - onLoginTapped: () {}, + await $.pumpApp(BlocProvider.value( + value: authCubit, + child: BlocProvider.value( + value: signupCubit, + child: DerivSignupLayout( + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () => isOnSignupPressedCalled = true, + onLoginTapped: () {}), ), )); @@ -134,22 +150,24 @@ void main() { .called(1); }); - patrolTest('onLoginTapped is called upon tapping login button', + patrolWidgetTest('onLoginTapped is called upon tapping login button', (PatrolTester $) async { bool isOnLoginTappedCalled = false; - await $.pumpApp(BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) {}, - onSingupError: (_) {}, - onSingupEmailSent: (_) {}, - onSignupPressed: () {}, - onLoginTapped: () { - isOnLoginTappedCalled = true; - }, + await $.pumpApp(BlocProvider.value( + value: authCubit, + child: BlocProvider.value( + value: signupCubit, + child: DerivSignupLayout( + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onLoginTapped: () { + isOnLoginTappedCalled = true; + }), ), )); @@ -161,7 +179,7 @@ void main() { expect(isOnLoginTappedCalled, true); }); - patrolTest('onSignupError is called upon signup error state', + patrolWidgetTest('onSignupError is called upon signup error state', (PatrolTester $) async { bool isOnSignupErrorCalled = false; @@ -171,20 +189,85 @@ void main() { when(() => signupCubit.stream).thenAnswer( (_) => Stream.fromIterable([const DerivSignupErrorState('')])); - await $.pumpApp(BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) {}, - onSingupError: (_) => isOnSignupErrorCalled = true, - onSingupEmailSent: (_) {}, - onSignupPressed: () {}, - onLoginTapped: () {}, + await $.pumpApp(BlocProvider.value( + value: authCubit, + child: BlocProvider.value( + value: signupCubit, + child: DerivSignupLayout( + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) => isOnSignupErrorCalled = true, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onLoginTapped: () {}), ), )); expect(isOnSignupErrorCalled, true); }); + patrolWidgetTest('onAuthError is called upon auth error state', + (PatrolTester $) async { + bool isOnAuthErrorCalled = false; + + final mockAuthState = DerivAuthErrorState( + isSocialLogin: false, + message: 'error', + type: AuthErrorType.failedAuthorization, + ); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream) + .thenAnswer((_) => Stream.fromIterable([mockAuthState])); + + await $.pumpApp(BlocProvider.value( + value: authCubit, + child: BlocProvider.value( + value: signupCubit, + child: DerivSignupLayout( + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onAuthError: (_) => isOnAuthErrorCalled = true, + onLoginTapped: () {}), + ), + )); + + expect(isOnAuthErrorCalled, true); + }); + + patrolWidgetTest('calls [AuthErrorStateHandler] on auth error state.', + (PatrolTester $) async { + final mockAuthState = DerivAuthErrorState( + isSocialLogin: false, + message: 'error', + type: AuthErrorType.failedAuthorization, + ); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream) + .thenAnswer((_) => Stream.fromIterable([mockAuthState])); + + await $.pumpApp(BlocProvider.value( + value: authCubit, + child: BlocProvider.value( + value: signupCubit, + child: DerivSignupLayout( + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onLoginTapped: () {}), + ))); + + expect($(PopupAlertDialog).$('Authorization failed.'), findsOneWidget); + }); }); } diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verification_done_layout_test.dart b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verification_done_layout_test.dart index 36bcb5202..b36fd6719 100644 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verification_done_layout_test.dart +++ b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verification_done_layout_test.dart @@ -1,13 +1,12 @@ import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../pump_app.dart'; void main() { group('DerivVerificationDoneLayout', () { - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp(DerivVerificationDoneLayout( verificationCode: '123456', onContinuePressed: () {}, @@ -18,7 +17,7 @@ void main() { expect($(ElevatedButton).$('Continue'), findsOneWidget); }); - patrolTest('onContinuePressed is called', (PatrolTester $) async { + patrolWidgetTest('onContinuePressed is called', (PatrolTester $) async { bool isContinuePressed = false; await $.pumpApp(DerivVerificationDoneLayout( diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verify_email_layout_test.dart b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verify_email_layout_test.dart index da35b53cc..3ba4740d3 100644 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verify_email_layout_test.dart +++ b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verify_email_layout_test.dart @@ -1,8 +1,7 @@ import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../pump_app.dart'; void main() { @@ -13,7 +12,7 @@ void main() { testEmail = 'test@gmail.com'; }); - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp(DerivVerifyEmailLayout( email: testEmail, onEmailNotReceivedPressed: () {}, @@ -25,7 +24,8 @@ void main() { expect($(Text).$('Check your email'), findsOneWidget); }); - patrolTest('onEmailNotReceivedPressed is called when tapped on the button', + patrolWidgetTest( + 'onEmailNotReceivedPressed is called when tapped on the button', (PatrolTester $) async { bool isEmailNotReceivedPressed = false; diff --git a/packages/deriv_auth_ui/test/features/signup/models/deriv_auth_utm_model_test.dart b/packages/deriv_auth_ui/test/features/signup/models/deriv_auth_utm_model_test.dart index daa546800..37d569f3c 100644 --- a/packages/deriv_auth_ui/test/features/signup/models/deriv_auth_utm_model_test.dart +++ b/packages/deriv_auth_ui/test/features/signup/models/deriv_auth_utm_model_test.dart @@ -2,11 +2,11 @@ import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; void main() { group('DerivAuthUtmModel', () { - patrolTest('Constructor initializes all properties correctly', + patrolWidgetTest('Constructor initializes all properties correctly', (PatrolTester $) async { final model = DerivAuthUtmModel( affiliateToken: 'affiliateToken', @@ -31,9 +31,10 @@ void main() { expect(model.affiliateToken, 'affiliateToken'); }); - patrolTest('Constructor initializes optional properties as null', + patrolWidgetTest('Constructor initializes optional properties as null', (PatrolTester $) async { - final model = DerivAuthUtmModel(utmSource: 'source', utmCampaign: 'campaign'); + final model = + DerivAuthUtmModel(utmSource: 'source', utmCampaign: 'campaign'); expect(model.utmCampaignId, isNull); expect(model.utmAdGroupId, isNull); diff --git a/packages/deriv_auth_ui/test/features/signup/models/deriv_password_policy_model_test.dart b/packages/deriv_auth_ui/test/features/signup/models/deriv_password_policy_model_test.dart index 6a23e8b08..02515b627 100644 --- a/packages/deriv_auth_ui/test/features/signup/models/deriv_password_policy_model_test.dart +++ b/packages/deriv_auth_ui/test/features/signup/models/deriv_password_policy_model_test.dart @@ -1,13 +1,13 @@ - // ignore_for_file: always_specify_types import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; void main() { group('DerivPasswordPolicyModel', () { - patrolTest('isMatchWith() returns true for matching password', (PatrolTester $) async { + patrolWidgetTest('isMatchWith() returns true for matching password', + (PatrolTester $) async { final policy = DerivPasswordPolicyModel( description: 'At least 8 characters', regex: RegExp(r'.{8,}'), @@ -16,7 +16,8 @@ void main() { expect(policy.isMatchWith('password123'), isTrue); }); - patrolTest('isMatchWith() returns false for non-matching password', (PatrolTester $) async { + patrolWidgetTest('isMatchWith() returns false for non-matching password', + (PatrolTester $) async { final policy = DerivPasswordPolicyModel( description: 'At least 8 characters', regex: RegExp(r'.{8,}'), @@ -25,7 +26,9 @@ void main() { expect(policy.isMatchWith('pass'), isFalse); }); - patrolTest('isMatchWith() returns true for optional policy with empty password', (PatrolTester $) async { + patrolWidgetTest( + 'isMatchWith() returns true for optional policy with empty password', + (PatrolTester $) async { final policy = DerivPasswordPolicyModel( description: 'Optional policy', regex: RegExp(r'^$'), diff --git a/packages/deriv_auth_ui/test/features/signup/models/deriv_residence_model_test.dart b/packages/deriv_auth_ui/test/features/signup/models/deriv_residence_model_test.dart index 9f1ca143a..e68a54495 100644 --- a/packages/deriv_auth_ui/test/features/signup/models/deriv_residence_model_test.dart +++ b/packages/deriv_auth_ui/test/features/signup/models/deriv_residence_model_test.dart @@ -2,11 +2,11 @@ import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; void main() { group('DerivResidenceModel', () { - patrolTest('Constructor initializes all properties correctly', + patrolWidgetTest('Constructor initializes all properties correctly', (PatrolTester $) async { const model = DerivResidenceModel( isDisabled: true, diff --git a/packages/deriv_auth_ui/test/features/signup/widgets/country_selection_list_widget_test.dart b/packages/deriv_auth_ui/test/features/signup/widgets/country_selection_list_widget_test.dart index 54ac56f6f..5ba424b15 100644 --- a/packages/deriv_auth_ui/test/features/signup/widgets/country_selection_list_widget_test.dart +++ b/packages/deriv_auth_ui/test/features/signup/widgets/country_selection_list_widget_test.dart @@ -2,8 +2,7 @@ import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:deriv_auth_ui/src/features/signup/widgets/country_selection_list_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../../pump_app.dart'; void main() { @@ -12,13 +11,14 @@ void main() { setUpAll(() { residences = [ - const DerivResidenceModel(code: 'ID', name: 'Indonesia', isDisabled: false), + const DerivResidenceModel( + code: 'ID', name: 'Indonesia', isDisabled: false), const DerivResidenceModel( code: 'UK', name: 'England', isDisabled: true), ]; }); - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp( CountrySelectionListWidget(countries: residences, onChanged: (_) {})); @@ -28,7 +28,7 @@ void main() { expect($(Text).$('England'), findsOneWidget); }); - patrolTest('onChanged is called', (PatrolTester $) async { + patrolWidgetTest('onChanged is called', (PatrolTester $) async { bool isOnChangedCalled = false; await $.pumpApp(CountrySelectionListWidget( @@ -40,7 +40,7 @@ void main() { expect(isOnChangedCalled, true); }); - patrolTest('search field appears on tapping search', + patrolWidgetTest('search field appears on tapping search', (PatrolTester $) async { await $.pumpApp(CountrySelectionListWidget( countries: residences, onChanged: (int country) => null)); diff --git a/packages/deriv_auth_ui/test/pump_app.dart b/packages/deriv_auth_ui/test/pump_app.dart index 3f74dd32a..a290d6116 100644 --- a/packages/deriv_auth_ui/test/pump_app.dart +++ b/packages/deriv_auth_ui/test/pump_app.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; import 'base_test_app.dart'; diff --git a/packages/deriv_banner/example/pubspec.yaml b/packages/deriv_banner/example/pubspec.yaml index 3a0e4b147..94c5b38b7 100644 --- a/packages/deriv_banner/example/pubspec.yaml +++ b/packages/deriv_banner/example/pubspec.yaml @@ -24,7 +24,7 @@ dev_dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_lint - ref: dev + ref: deriv_lint-v1.0.0 flutter: uses-material-design: true diff --git a/packages/deriv_banner/pubspec.yaml b/packages/deriv_banner/pubspec.yaml index 53b074ff2..dc7b16cb3 100644 --- a/packages/deriv_banner/pubspec.yaml +++ b/packages/deriv_banner/pubspec.yaml @@ -22,7 +22,7 @@ dev_dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_lint - ref: dev + ref: deriv_lint-v1.0.0 flutter: plugin: diff --git a/packages/deriv_bloc_manager/README.md b/packages/deriv_bloc_manager/README.md index fecfc8c77..11b77964b 100644 --- a/packages/deriv_bloc_manager/README.md +++ b/packages/deriv_bloc_manager/README.md @@ -51,7 +51,7 @@ This section shows how to create a new bloc/cubit following the proposed archite ```dart abstract class BaseStateListener {} -abstract class AuthStateListener implements BaseStateListener { +abstract class DerivAuthStateListener implements BaseStateListener { void onLogin(Authorize authorizedAccount); void onLogout(); @@ -64,7 +64,7 @@ abstract class ConnectivityStateListener implements BaseStateListener { } ``` -2- Create a cubit for a feature, let’s call it `FeatureCubit`. The cubit class will implement both `AuthStateListener` and `ConnectivityStateListener` so it can expose the 4 methods in addition to any other feature-specific states. The type of the state `FeatureCubit` is managing in this example, is **Status** with initial value as `initial`; +2- Create a cubit for a feature, let’s call it `FeatureCubit`. The cubit class will implement both `DerivAuthStateListener` and `ConnectivityStateListener` so it can expose the 4 methods in addition to any other feature-specific states. The type of the state `FeatureCubit` is managing in this example, is **Status** with initial value as `initial`; ```dart enum Status { @@ -82,7 +82,7 @@ The `FeatureCubit` will expose the common/share `onConnect`, `onDisconnect`, `on ```dart import 'package:bloc/bloc.dart'; -class FeatureCubit extends Cubit implements ConnectivityStateListener, AuthStateListener { +class FeatureCubit extends Cubit implements ConnectivityStateListener, DerivAuthStateListener { FeatureCubit() : super(Status.initial); void loading() => emit(Status.loading); diff --git a/packages/deriv_date_range_picker/CHANGELOG.md b/packages/deriv_date_range_picker/CHANGELOG.md index 41cc7d819..b7f2af439 100644 --- a/packages/deriv_date_range_picker/CHANGELOG.md +++ b/packages/deriv_date_range_picker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.1+1 + + - Update a dependency to the latest release. + ## 0.0.1 * TODO: Describe initial release. diff --git a/packages/deriv_date_range_picker/lib/src/date_range_picker.dart b/packages/deriv_date_range_picker/lib/src/date_range_picker.dart index f52a31216..3d5923708 100644 --- a/packages/deriv_date_range_picker/lib/src/date_range_picker.dart +++ b/packages/deriv_date_range_picker/lib/src/date_range_picker.dart @@ -237,7 +237,7 @@ class _DerivDateRangePickerState extends State { context.localization!.labelSelectedDateRange, style: context.theme.textStyle( textStyle: TextStyles.overline, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), diff --git a/packages/deriv_date_range_picker/lib/src/widgets/date_range_text_field.dart b/packages/deriv_date_range_picker/lib/src/widgets/date_range_text_field.dart index 0aa9a69bc..49440cca3 100644 --- a/packages/deriv_date_range_picker/lib/src/widgets/date_range_text_field.dart +++ b/packages/deriv_date_range_picker/lib/src/widgets/date_range_text_field.dart @@ -109,7 +109,7 @@ class _DateRangeTextFieldState extends State<_DateRangeTextField> { : TextInputAction.done, style: context.theme.textStyle( textStyle: TextStyles.subheading, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), decoration: InputDecoration( enabledBorder: OutlineInputBorder( diff --git a/packages/deriv_date_range_picker/lib/src/widgets/input_date_range.dart b/packages/deriv_date_range_picker/lib/src/widgets/input_date_range.dart index 9676a7bdf..50111d233 100644 --- a/packages/deriv_date_range_picker/lib/src/widgets/input_date_range.dart +++ b/packages/deriv_date_range_picker/lib/src/widgets/input_date_range.dart @@ -124,7 +124,7 @@ class _InputDateRangeState extends State { context.localization!.labelSelectedDateRange, style: context.theme.textStyle( textStyle: TextStyles.overline, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), diff --git a/packages/deriv_date_range_picker/lib/src/widgets/month_item.dart b/packages/deriv_date_range_picker/lib/src/widgets/month_item.dart index d9e6f5338..07a9167d7 100644 --- a/packages/deriv_date_range_picker/lib/src/widgets/month_item.dart +++ b/packages/deriv_date_range_picker/lib/src/widgets/month_item.dart @@ -157,7 +157,7 @@ class _MonthItemState extends State<_MonthItem> { localizations.formatMonthYear(widget.displayedMonth), style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), @@ -197,7 +197,7 @@ class _MonthItemState extends State<_MonthItem> { TextStyle itemStyle = context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ); BoxDecoration? decoration; diff --git a/packages/deriv_date_range_picker/pubspec.yaml b/packages/deriv_date_range_picker/pubspec.yaml index d24ac1883..a188da8a5 100644 --- a/packages/deriv_date_range_picker/pubspec.yaml +++ b/packages/deriv_date_range_picker/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_date_range_picker description: A new Flutter package project. -version: 0.0.1 +version: 0.0.1+1 publish_to: "none" # Remove this line if you wish to publish to pub.dev environment: @@ -17,7 +17,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_theme - ref: dev + ref: deriv_theme-v2.1.0 intl: ^0.18.0 diff --git a/packages/deriv_env/CHANGELOG.md b/packages/deriv_env/CHANGELOG.md index 761894f12..ecd5433ba 100644 --- a/packages/deriv_env/CHANGELOG.md +++ b/packages/deriv_env/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.1+1 + + - **REFACTOR**(deriv_env): make package independent of env file ([#318](https://github.com/regentmarkets/flutter-deriv-packages/issues/318)). ([a7242c81](https://github.com/regentmarkets/flutter-deriv-packages/commit/a7242c81b97fda70a622d7bbbb97fe997067117a)) + ## 0.0.1 - initial release. diff --git a/packages/deriv_env/README.md b/packages/deriv_env/README.md index c03ca600a..43862781b 100644 --- a/packages/deriv_env/README.md +++ b/packages/deriv_env/README.md @@ -31,12 +31,12 @@ You can check if the environment variable is initialized using the `isInitialize bool isInitialized = Env().isInitialized; ``` -### Loading environment variables +### Initializing the Env -Before using any environment variables, you need to load them from a file. The `load` method loads the variables from a file with the specified filename (default is `.env`). +Before using any environment variables, you need to intialize the `Env` class and pass it an instnace of the EnvLoader and the path to the file. The `initialize` method loads the variables from a file with the specified filename (default is `.env`). ```dart -await Env().load(); +await Env().initialize(EnvLoader(filePath: '.env')); ``` The load method expects the file to contain key-value pairs separated by an equals sign (`=`) and each pair separated by a newline character (`\n`). Blank lines and comments (lines starting with a `#`) are ignored. diff --git a/packages/deriv_env/analysis_options.yaml b/packages/deriv_env/analysis_options.yaml index 91390bcb1..bf3a92f06 100644 --- a/packages/deriv_env/analysis_options.yaml +++ b/packages/deriv_env/analysis_options.yaml @@ -58,12 +58,11 @@ linter: - flutter_style_todos - hash_and_equals - implementation_imports - - iterable_contains_unrelated_type + - collection_methods_unrelated_type - join_return_with_assignment - library_names - library_prefixes # - lines_longer_than_80_chars - - list_remove_unrelated_type - no_adjacent_strings_in_list - no_duplicate_case_values - no_leading_underscores_for_local_identifiers diff --git a/packages/deriv_env/lib/base_env.dart b/packages/deriv_env/lib/base_env_loader.dart similarity index 55% rename from packages/deriv_env/lib/base_env.dart rename to packages/deriv_env/lib/base_env_loader.dart index 24700d332..59afabd53 100644 --- a/packages/deriv_env/lib/base_env.dart +++ b/packages/deriv_env/lib/base_env_loader.dart @@ -1,16 +1,19 @@ /// Base class for retrieve environment variables providers. -abstract class BaseEnv { +abstract class BaseEnvLoader { /// Returns `true` if [Env] is initialized, otherwise `false`. bool get isInitialized; /// Returns all environment variables as a [Map]. Map get entries; - /// Loads environment variables from a `.env` file. - /// - /// If [filename] is not provided, it will default to `.env`. - Future load([String filename = '.env']); + /// Loads environment variables. + Future loadEnvironment(); /// Retrieves an environment variable value by key. - T get(String key, {T? defaultValue}); + T get( + String key, { + T? defaultValue, + T Function(String value)? parser, + String decryptionKey = '', + }); } diff --git a/packages/deriv_env/lib/deriv_env.dart b/packages/deriv_env/lib/deriv_env.dart new file mode 100644 index 000000000..27ef791bf --- /dev/null +++ b/packages/deriv_env/lib/deriv_env.dart @@ -0,0 +1,3 @@ +export 'env.dart'; +export 'env_loader.dart'; +export 'base_env_loader.dart'; diff --git a/packages/deriv_env/lib/env.dart b/packages/deriv_env/lib/env.dart index 2d6af2877..3383f64a4 100644 --- a/packages/deriv_env/lib/env.dart +++ b/packages/deriv_env/lib/env.dart @@ -1,114 +1,56 @@ -import 'package:flutter/services.dart'; - -import 'base_env.dart'; -import 'cipher.dart'; - -/// [Env] class is a singleton class that provides access to environment variables. -class Env extends BaseEnv { - /// Returns the singleton instance of [Env]. - factory Env() => _instance; - - Env._(); +import 'base_env_loader.dart'; +import 'env_loader.dart'; + +/// This class is used to load environment variables from a .env file +class Env { + /// The singleton instance of [EnvLoader]. + factory Env() { + _instance ??= Env._internal(); + return _instance!; + } - static final Env _instance = Env._(); + Env._internal(); - bool _isInitialized = false; + static Env? _instance; - final Map _entries = {}; + /// The environment variables provider. + BaseEnvLoader? _env; - @override - bool get isInitialized => _isInitialized; + /// The instance of [BaseEnv]. + BaseEnvLoader? get env => _env; - @override - Map get entries { - _checkInitialization(); + /// Returns `true` if [Env] is initialized, otherwise `false`. + bool get isInitialized => _env?.isInitialized ?? false; - return _entries; + /// Initializes [EnvLoader] with an instance of [BaseEnv]. + /// Loads environment variables from a `.env` file. + /// + /// If [filename] is not provided, it will default to `.env`. + Future initialize(BaseEnvLoader env) async { + _env = env; + return _env!.loadEnvironment(); } - @override - Future load([String filename = '.env']) async { - _entries.clear(); - - final List fileEntries = await _getEntriesFromFile(filename); - - for (final String entry in fileEntries) { - final List items = entry.split('='); - - if (items.length > 1) { - _entries[items.first.trim()] = items.sublist(1).join('=').trim(); - } - } - - _isInitialized = true; + /// Loads environment variables from a `.env` file. + @Deprecated('Use initialize() method instead.') + Future load([String filePath = '.env']) async { + _env ??= EnvLoader(filePath: filePath); + return _env!.loadEnvironment(); } - @override + /// Retrieves an environment variable value by key. T get( String key, { T? defaultValue, T Function(String value)? parser, String decryptionKey = '', - }) { - _checkInitialization(); - - if (!_entries.containsKey(key)) { - if (defaultValue == null) { - throw Exception('$runtimeType does not contain a value for key: $key.'); - } - - return defaultValue; - } - - final String value = decryptionKey.isEmpty - ? _entries[key] - : Cipher().decrypt(message: _entries[key], key: decryptionKey); - - if (parser != null) { - return parser(value); - } - - switch (T) { - case int: - return int.tryParse(value) as T; - case double: - return double.tryParse(value) as T; - case bool: - return (value.toLowerCase() == 'true') as T; - - default: - return value as T; - } - } - - Future> _getEntriesFromFile(String filename) async { - final String envFileContent = await rootBundle.loadString(filename); - - if (envFileContent.isEmpty) { - throw Exception('$runtimeType: $filename is empty.'); - } - - final List entries = []; - final List content = envFileContent.split('\n'); - - for (final String line in content) { - if (line.isEmpty || line.startsWith('#')) { - continue; - } - - entries.add(line); - } - - return entries; - } - - void _checkInitialization() { - if (_isInitialized) { - return; - } - - throw Exception( - '$runtimeType is not initialized, call load() method first.', - ); - } + }) => + isInitialized + ? _env!.get( + key, + defaultValue: defaultValue, + parser: parser, + decryptionKey: decryptionKey, + ) + : throw Exception('EnvLoader is not initialized.'); } diff --git a/packages/deriv_env/lib/env_loader.dart b/packages/deriv_env/lib/env_loader.dart new file mode 100644 index 000000000..c3a8d9412 --- /dev/null +++ b/packages/deriv_env/lib/env_loader.dart @@ -0,0 +1,119 @@ +import 'package:flutter/services.dart'; + +import 'base_env_loader.dart'; +import 'cipher.dart'; + +/// [Env] class is a singleton class that provides access to environment variables. +class EnvLoader extends BaseEnvLoader { + /// Creates a new instance of [EnvLoader]. + EnvLoader({required this.filePath}); + + /// The path to the file. + final String filePath; + + bool _isInitialized = false; + + final Map _entries = {}; + + @override + bool get isInitialized => _isInitialized; + + @override + Map get entries { + _checkInitialization(); + + return _entries; + } + + @override + Future loadEnvironment() async { + _entries.clear(); + + final List fileEntries = await _getEntriesFromFile(filePath); + + for (final String entry in fileEntries) { + final List items = entry.split('='); + + if (items.length > 1) { + _entries[items.first.trim()] = items.sublist(1).join('=').trim(); + } + } + + _isInitialized = true; + } + + @override + T get( + String key, { + T? defaultValue, + T Function(String value)? parser, + String decryptionKey = '', + }) { + _checkInitialization(); + + if (!_entries.containsKey(key)) { + if (defaultValue == null) { + throw Exception('$runtimeType does not contain a value for key: $key.'); + } + + return defaultValue; + } + + final String value = decryptionKey.isEmpty + ? _entries[key] + : Cipher().decrypt(message: _entries[key], key: decryptionKey); + + if (parser != null) { + return parser(value); + } + + switch (T) { + case int: + return int.tryParse(value) as T; + case double: + return double.tryParse(value) as T; + case bool: + if (value.toLowerCase() == 'true') { + return true as T; + } else if (value.toLowerCase() == 'false') { + return false as T; + } else { + throw FormatException('Invalid boolean value: $value'); + } + + default: + return value as T; + } + } + + Future> _getEntriesFromFile(String filename) async { + final String envFileContent = await rootBundle.loadString(filename); + + if (envFileContent.isEmpty) { + throw Exception('$runtimeType: $filename is empty.'); + } + + final List entries = []; + final List content = envFileContent.split('\n'); + + for (final String line in content) { + if (line.isEmpty || line.startsWith('#')) { + continue; + } + + entries.add(line); + } + + return entries; + } + + void _checkInitialization() { + if (_isInitialized) { + return; + } + + throw Exception( + '$runtimeType is not initialized, call load() method first.', + ); + } +} diff --git a/packages/deriv_env/pubspec.yaml b/packages/deriv_env/pubspec.yaml index ef12510bc..b12aa29ec 100644 --- a/packages/deriv_env/pubspec.yaml +++ b/packages/deriv_env/pubspec.yaml @@ -1,7 +1,7 @@ name: deriv_env description: A package to load and store environment variables. -version: 0.0.1 +version: 0.0.1+1 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/deriv_env/test/cipher_test.dart b/packages/deriv_env/test/cipher_test.dart index 475facd4c..892633dba 100644 --- a/packages/deriv_env/test/cipher_test.dart +++ b/packages/deriv_env/test/cipher_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:deriv_env/Cipher.dart'; +import 'package:deriv_env/cipher.dart'; void main() { group('cipher tests =>', () { diff --git a/packages/deriv_env/test/env_test.dart b/packages/deriv_env/test/env_test.dart index 1221a7108..59b338fca 100644 --- a/packages/deriv_env/test/env_test.dart +++ b/packages/deriv_env/test/env_test.dart @@ -1,3 +1,4 @@ +import 'package:deriv_env/env_loader.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:deriv_env/env.dart'; @@ -5,6 +6,14 @@ import 'package:deriv_env/env.dart'; void main() { setUpAll(() => TestWidgetsFlutterBinding.ensureInitialized()); + const String stringKey = 'STRING_VAR'; + const String intKey = 'INT_VAR'; + const String doubleKey = 'DOUBLE_VAR'; + const String boolKey = 'BOOL_VAR'; + const String varWithEqualsKey = 'VAR_WITH_EQUALS'; + const String varWithHashKey = 'VAR_WITH_HASH'; + const String encryptedKey = 'ENCRYPTED_VAR'; + group('env class test =>', () { test('get() method should throw exception if env is not initialized.', () async { @@ -14,27 +23,27 @@ void main() { test('load() method should populate env map.', () async { expect(Env().isInitialized, isFalse); - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect(Env().isInitialized, isTrue); - expect(Env().entries.length, 7); + expect(Env().env!.entries.length, 7); - expect(Env().entries['STRING_VAR'], 'hello world'); - expect(Env().entries['INT_VAR'], '123'); - expect(Env().entries['DOUBLE_VAR'], '3.14'); - expect(Env().entries['BOOL_VAR'], 'true'); - expect(Env().entries['VAR_WITH_EQUALS'], 'hello=world'); - expect(Env().entries['VAR_WITH_HASH'], 'hello#world'); + expect(Env().env!.entries[stringKey], 'hello world'); + expect(Env().env!.entries[intKey], '123'); + expect(Env().env!.entries[doubleKey], '3.14'); + expect(Env().env!.entries[boolKey], 'true'); + expect(Env().env!.entries[varWithEqualsKey], 'hello=world'); + expect(Env().env!.entries[varWithHashKey], 'hello#world'); expect( - Env().entries['ENCRYPTED_VAR'], + Env().env!.entries[encryptedKey], 'dVyH3QjdHYcjcS2TQ1XenmDVvf5ViN8ZpSVEcjfFhsk=', ); }); test('get() method should return default value if key is not found.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect( Env().get('INVALID_KEY', defaultValue: 'default'), @@ -43,31 +52,31 @@ void main() { }); test('get() method should parse value as int.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); - expect(Env().get('INT_VAR'), 123); + expect(Env().get(intKey), 123); }); test('get() method should parse value as double.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); - expect(Env().get('DOUBLE_VAR'), 3.14); + expect(Env().get(doubleKey), 3.14); }); test('get() method should parse value as bool.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); - expect(Env().get('BOOL_VAR'), isTrue); + expect(Env().get(boolKey), isTrue); }); test( 'get() method should parse value with a parser factory if it is provided.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect( Env().get( - 'STRING_VAR', + stringKey, parser: (String value) => value.toUpperCase(), ), 'HELLO WORLD', @@ -75,7 +84,7 @@ void main() { expect( Env().get( - 'INT_VAR', + intKey, parser: (String value) => int.parse(value) * 2, ), 246, @@ -83,7 +92,7 @@ void main() { expect( Env().get( - 'DOUBLE_VAR', + doubleKey, parser: (String value) => double.parse(value) * 2, ), 6.28, @@ -91,7 +100,7 @@ void main() { expect( Env().get( - 'DOUBLE_VAR', + doubleKey, parser: (String value) => double.parse(value) > 3.14, ), false, @@ -100,18 +109,18 @@ void main() { test('check handling variables with special characters like `#` and `=`.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); - expect(Env().entries['VAR_WITH_EQUALS'], 'hello=world'); - expect(Env().entries['VAR_WITH_HASH'], 'hello#world'); + expect(Env().env!.entries[varWithEqualsKey], 'hello=world'); + expect(Env().env!.entries[varWithHashKey], 'hello#world'); }); test('handle encrypted variable.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect( Env().get( - 'ENCRYPTED_VAR', + encryptedKey, decryptionKey: 'TbKjMndW1L8vczgGQfPo2IyUxh6XAEay', ), 'ecnrypted message', @@ -119,7 +128,9 @@ void main() { }); test('throws an exception if file is empty.', () async { - expect(() => Env().load('test/.env.empty.test'), throwsException); + expect( + () => Env().initialize(EnvLoader(filePath: 'test/.env.empty.test')), + throwsException); }); test('throws an exception if env is not initialized.', () async { @@ -127,7 +138,7 @@ void main() { }); test('throws an exception if key is not found.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect(() => Env().get('INVALID_KEY'), throwsException); }); @@ -135,7 +146,7 @@ void main() { test( 'does not throw an exception if key is not found and a default value is provided.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect(() => Env().get('INVALID_KEY', defaultValue: 42), returnsNormally); }); diff --git a/packages/deriv_expandable_bottom_sheet/CHANGELOG.md b/packages/deriv_expandable_bottom_sheet/CHANGELOG.md index 41cc7d819..b7f2af439 100644 --- a/packages/deriv_expandable_bottom_sheet/CHANGELOG.md +++ b/packages/deriv_expandable_bottom_sheet/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.1+1 + + - Update a dependency to the latest release. + ## 0.0.1 * TODO: Describe initial release. diff --git a/packages/deriv_expandable_bottom_sheet/pubspec.yaml b/packages/deriv_expandable_bottom_sheet/pubspec.yaml index cfa726683..2b33fe075 100644 --- a/packages/deriv_expandable_bottom_sheet/pubspec.yaml +++ b/packages/deriv_expandable_bottom_sheet/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_expandable_bottom_sheet description: A new Flutter package project. -version: 0.0.1 +version: 0.0.1+1 publish_to: none environment: @@ -15,7 +15,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_theme - ref: dev + ref: deriv_theme-v2.1.0 dev_dependencies: flutter_test: diff --git a/packages/deriv_lint/CHANGELOG.md b/packages/deriv_lint/CHANGELOG.md new file mode 100644 index 000000000..139597f9c --- /dev/null +++ b/packages/deriv_lint/CHANGELOG.md @@ -0,0 +1,2 @@ + + diff --git a/packages/deriv_live_chat/pubspec.yaml b/packages/deriv_live_chat/pubspec.yaml index 0a33777ff..a68f13baf 100644 --- a/packages/deriv_live_chat/pubspec.yaml +++ b/packages/deriv_live_chat/pubspec.yaml @@ -20,7 +20,7 @@ dev_dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_lint - ref: dev + ref: deriv_lint-v1.0.0 flutter: plugin: diff --git a/packages/deriv_numpad/CHANGELOG.md b/packages/deriv_numpad/CHANGELOG.md new file mode 100644 index 000000000..e9bf0b29c --- /dev/null +++ b/packages/deriv_numpad/CHANGELOG.md @@ -0,0 +1,4 @@ +## 1.0.1 + + - Update a dependency to the latest release. + diff --git a/packages/deriv_numpad/lib/core/widgets/custom_checkbox.dart b/packages/deriv_numpad/lib/core/widgets/custom_checkbox.dart index 4f0b4938a..5a7476e56 100644 --- a/packages/deriv_numpad/lib/core/widgets/custom_checkbox.dart +++ b/packages/deriv_numpad/lib/core/widgets/custom_checkbox.dart @@ -50,7 +50,7 @@ class CustomCheckbox extends StatelessWidget { textAlign: TextAlign.left, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), diff --git a/packages/deriv_numpad/pubspec.yaml b/packages/deriv_numpad/pubspec.yaml index b16596119..cdc86e058 100644 --- a/packages/deriv_numpad/pubspec.yaml +++ b/packages/deriv_numpad/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_numpad description: A new Flutter project. -version: 1.0.0+1 +version: 1.0.1 publish_to: "none" environment: @@ -13,7 +13,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_theme - ref: dev + ref: deriv_theme-v2.1.0 flutter_svg: ^1.0.3 intl: ^0.17.0 diff --git a/packages/deriv_store_launcher/example/pubspec.yaml b/packages/deriv_store_launcher/example/pubspec.yaml index fecf5aef5..2b2f639da 100644 --- a/packages/deriv_store_launcher/example/pubspec.yaml +++ b/packages/deriv_store_launcher/example/pubspec.yaml @@ -24,7 +24,7 @@ dev_dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_lint - ref: dev + ref: deriv_lint-v1.0.0 flutter: uses-material-design: true diff --git a/packages/deriv_store_launcher/pubspec.yaml b/packages/deriv_store_launcher/pubspec.yaml index c59960d45..76666e1a2 100644 --- a/packages/deriv_store_launcher/pubspec.yaml +++ b/packages/deriv_store_launcher/pubspec.yaml @@ -22,7 +22,7 @@ dev_dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_lint - ref: dev + ref: deriv_lint-v1.0.0 flutter: plugin: diff --git a/packages/deriv_technical_analysis/lib/src/indicators/calculations/ema_indicator.dart b/packages/deriv_technical_analysis/lib/src/indicators/calculations/ema_indicator.dart index af0dfea00..26b3b1310 100644 --- a/packages/deriv_technical_analysis/lib/src/indicators/calculations/ema_indicator.dart +++ b/packages/deriv_technical_analysis/lib/src/indicators/calculations/ema_indicator.dart @@ -32,6 +32,24 @@ abstract class AbstractEMAIndicator prevValue, ); } + + @override + void copyValuesFrom(covariant AbstractEMAIndicator other) { + super.copyValuesFrom(other); + if (indicator is CachedIndicator) { + (indicator as CachedIndicator) + .copyValuesFrom(other.indicator as CachedIndicator); + } + } + + @override + void invalidate(int index) { + super.invalidate(index); + + if (indicator is CachedIndicator) { + (indicator as CachedIndicator).invalidate(index); + } + } } /// EMA indicator diff --git a/packages/deriv_theme/CHANGELOG.md b/packages/deriv_theme/CHANGELOG.md index 2087a73ac..b92d4999e 100644 --- a/packages/deriv_theme/CHANGELOG.md +++ b/packages/deriv_theme/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.0 + + - **FEAT**: Ayaan/Added margins 112 and 164 ([#319](https://github.com/regentmarkets/flutter-deriv-packages/issues/319)). ([bf625820](https://github.com/regentmarkets/flutter-deriv-packages/commit/bf6258206b4bb4cbdd1ef6744e07e2adb8d0d5ee)) + ## [2.0.0] - Migrated to null safety diff --git a/packages/deriv_theme/lib/src/dimens.dart b/packages/deriv_theme/lib/src/dimens.dart index b13f4812d..51ae29b44 100644 --- a/packages/deriv_theme/lib/src/dimens.dart +++ b/packages/deriv_theme/lib/src/dimens.dart @@ -73,12 +73,18 @@ class Dimens { /// 96 pixels margin. static const double margin96 = 96; + /// 112 pixels margin. + static const double margin112 = 112; + /// 128 pixels margin. static const double margin128 = 128; /// 144 pixels margin. static const double margin144 = 144; + /// 164 pixels margin. + static const double margin164 = 164; + /// 176 pixels margin. static const double margin176 = 176; diff --git a/packages/deriv_theme/lib/theme_provider.dart b/packages/deriv_theme/lib/theme_provider.dart index b9bc21ec5..2fc100c52 100644 --- a/packages/deriv_theme/lib/theme_provider.dart +++ b/packages/deriv_theme/lib/theme_provider.dart @@ -66,10 +66,14 @@ class ThemeProvider { static const double margin96 = Dimens.margin96; + static const double margin112 = Dimens.margin112; + static const double margin128 = Dimens.margin128; static const double margin144 = Dimens.margin144; + static const double margin164 = Dimens.margin164; + static const double margin176 = Dimens.margin176; static const double margin192 = Dimens.margin192; diff --git a/packages/deriv_theme/pubspec.yaml b/packages/deriv_theme/pubspec.yaml index a8a2db7e4..79813bdcf 100644 --- a/packages/deriv_theme/pubspec.yaml +++ b/packages/deriv_theme/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_theme description: A flutter package that contains the theme used by deriv products -version: 2.0.0 +version: 2.1.0 homepage: https://deriv.com/ publish_to: "none" diff --git a/packages/deriv_ui/CHANGELOG.md b/packages/deriv_ui/CHANGELOG.md index 41cc7d819..2a58aaa09 100644 --- a/packages/deriv_ui/CHANGELOG.md +++ b/packages/deriv_ui/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.0.1+2 + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + +## 0.0.1+1 + + - Update a dependency to the latest release. + ## 0.0.1 * TODO: Describe initial release. diff --git a/packages/deriv_ui/lib/presentation/widgets/base_text_field.dart b/packages/deriv_ui/lib/presentation/widgets/base_text_field.dart index f50a1b9f1..7aeeff8e6 100644 --- a/packages/deriv_ui/lib/presentation/widgets/base_text_field.dart +++ b/packages/deriv_ui/lib/presentation/widgets/base_text_field.dart @@ -32,6 +32,7 @@ class BaseTextField extends StatefulWidget { this.onEditingComplete, this.onChanged, this.onTap, + this.semanticLabel, Key? key, }) : super(key: key); @@ -113,6 +114,9 @@ class BaseTextField extends StatefulWidget { /// onTap callback function. final VoidCallback? onTap; + /// Semantic label. + final String? semanticLabel; + @override _BaseTextFieldState createState() => _BaseTextFieldState(); } @@ -132,72 +136,73 @@ class _BaseTextFieldState extends State { } @override - Widget build(BuildContext context) => TextFormField( - key: _formFieldKey, - controller: widget.controller, - focusNode: widget.focusNode, - initialValue: widget.initialValue, - keyboardType: widget.keyboardType, - maxLength: widget.maxLength, - autocorrect: false, - readOnly: widget.readOnly, - enabled: widget.enabled, - obscureText: widget.obscureText, - textInputAction: widget.textInputAction, - inputFormatters: widget.inputFormatters, - decoration: InputDecoration( - labelText: widget.labelText, - border: const OutlineInputBorder(), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: widget.borderColor ?? context.theme.colors.hover, + Widget build(BuildContext context) => Semantics( + explicitChildNodes: true, + label: widget.semanticLabel, + child: TextFormField( + key: _formFieldKey, + controller: widget.controller, + focusNode: widget.focusNode, + initialValue: widget.initialValue, + keyboardType: widget.keyboardType, + maxLength: widget.maxLength, + autocorrect: false, + readOnly: widget.readOnly, + enabled: widget.enabled, + obscureText: widget.obscureText, + textInputAction: widget.textInputAction, + inputFormatters: widget.inputFormatters, + decoration: InputDecoration( + labelText: widget.labelText, + border: const OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: widget.borderColor ?? context.theme.colors.hover, + ), + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), ), - borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: - widget.focusedBorderColor ?? context.theme.colors.blue, + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: widget.focusedBorderColor ?? context.theme.colors.blue, + ), ), - borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(color: context.theme.colors.coral), - borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), - ), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide(color: context.theme.colors.coral), - borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), - ), - labelStyle: context.theme.textStyle( - textStyle: TextStyles.subheading, - color: _hasError - ? context.theme.colors.coral - : _hasFocus() - ? widget.focusedLabelColor ?? - context.theme.colors.blue - : widget.labelColor ?? context.theme.colors.disabled, - ), - counterText: widget.showCounterText ? null : '', - errorMaxLines: widget.errorMaxLines, - prefix: widget.prefix, - prefixStyle: context.theme.textStyle( - textStyle: TextStyles.subheading, - color: _getTextFieldColor(), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: context.theme.colors.coral), + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide(color: context.theme.colors.coral), + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), + ), + labelStyle: context.theme.textStyle( + textStyle: TextStyles.subheading, + color: _hasError + ? context.theme.colors.coral + : _hasFocus() + ? widget.focusedLabelColor ?? context.theme.colors.blue + : widget.labelColor ?? context.theme.colors.disabled, + ), + counterText: widget.showCounterText ? null : '', + errorMaxLines: widget.errorMaxLines, + prefix: widget.prefix, + prefixStyle: context.theme.textStyle( + textStyle: TextStyles.subheading, + color: _getTextFieldColor(), + ), + suffixIcon: widget.suffixIcon, ), - suffixIcon: widget.suffixIcon, + onEditingComplete: widget.onEditingComplete, + validator: _validator, + onChanged: _onChanged, + onTap: widget.onTap, ), - onEditingComplete: widget.onEditingComplete, - validator: _validator, - onChanged: _onChanged, - onTap: widget.onTap, ); bool _hasFocus() => widget.focusNode?.hasFocus ?? false; Color _getTextFieldColor() => widget.enabled && _hasFocus() ? widget.focusedTextColor ?? context.theme.colors.prominent - : widget.textColor ?? context.theme.colors.lessProminent; + : widget.textColor ?? context.theme.colors.general; String? _validator(String? input) { final String? errorMsg = widget.validator?.call(input); diff --git a/packages/deriv_ui/lib/presentation/widgets/custom_checkbox.dart b/packages/deriv_ui/lib/presentation/widgets/custom_checkbox.dart index e47177718..352ec2afb 100644 --- a/packages/deriv_ui/lib/presentation/widgets/custom_checkbox.dart +++ b/packages/deriv_ui/lib/presentation/widgets/custom_checkbox.dart @@ -8,6 +8,8 @@ class CustomCheckbox extends StatelessWidget { required this.message, this.value = false, this.onValueChanged, + this.padding = const EdgeInsets.only(top: ThemeProvider.margin08), + this.contentsVerticalAlignment = CrossAxisAlignment.center, Key? key, }) : super(key: key); @@ -20,11 +22,24 @@ class CustomCheckbox extends StatelessWidget { /// A callback for the listeners of a checkbox, when the checkbox's value changes. final Function({bool? isChecked})? onValueChanged; + /// Padding of the checkbox. + final EdgeInsetsGeometry padding; + + /// The vertical alignment of the check and message within the row. + /// Defaults to [CrossAxisAlignment.center]. + /// + /// Example use case: + /// If the checkbox's message is longer than the width of the screen, + /// the message will be wrapped to the multiple line. Then, the message and + /// the checkbox can be aligned vertically top, center, or bottom of the row. + final CrossAxisAlignment contentsVerticalAlignment; + @override Widget build(BuildContext context) => Container( - padding: const EdgeInsets.only(top: ThemeProvider.margin08), + padding: padding, transform: Matrix4.translationValues(-14, 0, 0), child: Row( + crossAxisAlignment: contentsVerticalAlignment, children: [ Theme( data: ThemeData( @@ -47,7 +62,7 @@ class CustomCheckbox extends StatelessWidget { textAlign: TextAlign.left, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), onTap: onValueChanged == null diff --git a/packages/deriv_ui/lib/utils/popup_dialogs_helper.dart b/packages/deriv_ui/lib/utils/popup_dialogs_helper.dart index c70e69b7d..bb5e11de1 100644 --- a/packages/deriv_ui/lib/utils/popup_dialogs_helper.dart +++ b/packages/deriv_ui/lib/utils/popup_dialogs_helper.dart @@ -138,7 +138,7 @@ Future showSimpleLoadingDialog( bodyMessage, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), textAlign: TextAlign.center, ), @@ -172,7 +172,7 @@ Future showErrorDialog({ errorMessage ?? '', style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), positiveActionLabel: actionLabel, @@ -195,7 +195,7 @@ Future showTokenExpiredDialog({ content, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), positiveActionLabel: positiveActionLabel, @@ -223,7 +223,7 @@ Future showAccountDeactivatedDialog({ text: content, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), children: [ buildTextSpanHyperlink( diff --git a/packages/deriv_ui/lib/utils/regex_helpers.dart b/packages/deriv_ui/lib/utils/regex_helpers.dart index 1a1d6e839..adfdfb75b 100644 --- a/packages/deriv_ui/lib/utils/regex_helpers.dart +++ b/packages/deriv_ui/lib/utils/regex_helpers.dart @@ -20,3 +20,10 @@ RegExp validPasswordLengthRegex = RegExp(r'^.{8,25}$'); /// Valid Password length for login. RegExp validLoginPasswordLengthRegex = RegExp(r'^.{6,25}$'); + +/// Check if [str] input contains only a-z letters and 0-9 numbers +bool hasOnlySmallLettersAndNumberInput(String str) => + RegExp('^[a-z0-9.]+\$').hasMatch(str); + +/// Check if [string] input contains only 0-9 numbers +bool hasOnlyNumberInput(String string) => RegExp('^[0-9]+\$').hasMatch(string); diff --git a/packages/deriv_ui/pubspec.yaml b/packages/deriv_ui/pubspec.yaml index 36734d6d8..8201c1b5a 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.1 +version: 0.0.1+2 publish_to: none environment: @@ -12,18 +12,16 @@ dependencies: sdk: flutter deriv_theme: - path: - ../deriv_theme - # git: - # url: git@github.com:regentmarkets/flutter-deriv-packages.git - # path: packages/deriv_theme - # ref: dev + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_theme + ref: deriv_theme-v2.1.0 deriv_web_view: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_web_view - ref: dev + ref: deriv_web_view-v0.0.1 flutter_svg: ^1.0.3 diff --git a/packages/deriv_web_view/pubspec.yaml b/packages/deriv_web_view/pubspec.yaml index 80178e50c..399fc7bf4 100644 --- a/packages/deriv_web_view/pubspec.yaml +++ b/packages/deriv_web_view/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_http_client - ref: dev + ref: deriv_http_client-v1.0.0 dev_dependencies: flutter_test: diff --git a/packages/update_checker/example/lib/update_bloc_page.dart b/packages/update_checker/example/lib/update_bloc_page.dart index 9afd733fd..55b0b079f 100644 --- a/packages/update_checker/example/lib/update_bloc_page.dart +++ b/packages/update_checker/example/lib/update_bloc_page.dart @@ -9,7 +9,8 @@ class UpdateBlocPage extends StatefulWidget { } class _UpdateBlocPageState extends State { - final UpdateBloc updateBloc = UpdateBloc(); + final UpdateBloc updateBloc = + UpdateBloc(firebaseRepository: const 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 1440f52a5..1d54f4043 100644 --- a/packages/update_checker/example/lib/update_checker_page.dart +++ b/packages/update_checker/example/lib/update_checker_page.dart @@ -10,6 +10,7 @@ class UpdateCheckerPage extends StatelessWidget { ), body: Builder( builder: (BuildContext context) => UpdateChecker( + firebaseRepository: const FirebaseRemoteConfigRepository(), onNotAvailable: () => _showSnackBar( context, 'Update not available', diff --git a/packages/update_checker/lib/src/bloc/update_bloc.dart b/packages/update_checker/lib/src/bloc/update_bloc.dart index 84f3544a1..4c1865523 100644 --- a/packages/update_checker/lib/src/bloc/update_bloc.dart +++ b/packages/update_checker/lib/src/bloc/update_bloc.dart @@ -1,13 +1,16 @@ import 'dart:convert'; +import 'dart:io'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:update_checker/src/repositories/base_firebase.dart'; import '../models/models.dart'; import '../repositories/repositories.dart'; import '../utils/utils.dart'; part 'update_event.dart'; + part 'update_state.dart'; /// UpdateBloc is responsible for fetching the update availability from the @@ -17,7 +20,7 @@ class UpdateBloc extends Bloc { /// availability from the Firebase Database and `PackageInfoRepository` to /// check the app version with the update to determine the update availability UpdateBloc({ - this.firebaseDatabaseRepository = const FirebaseDatabaseRepository(), + required this.firebaseRepository, this.packageInfoRepository = const PackageInfoRepository(), }) : super(UpdateInitialState()) { on( @@ -27,7 +30,7 @@ class UpdateBloc extends Bloc { } /// Firebase database repository for fetching the update information. - final FirebaseDatabaseRepository firebaseDatabaseRepository; + final BaseFirebase firebaseRepository; /// Package info repository for fetching the app build number. final PackageInfoRepository packageInfoRepository; @@ -36,7 +39,10 @@ class UpdateBloc extends Bloc { UpdateFetchEvent event, Emitter emit) async { if (state is! UpdateInProgressState) { emit(UpdateInProgressState()); - final UpdateInfo? info = await _getUpdateInfo(); + final UpdateInfo? info = + (firebaseRepository is FirebaseRemoteConfigRepository) + ? await _getUpdateInfoFromRemoteConfig() + : await _getUpdateInfoFromDatabase(); if (info != null) { emit(UpdateAvailableState(info)); } else { @@ -45,8 +51,58 @@ class UpdateBloc extends Bloc { } } - Future _getUpdateInfo() async { - final dynamic rawData = await firebaseDatabaseRepository.fetchUpdateData(); + String? _platformName() { + if (Platform.isAndroid || Platform.isIOS) { + return Platform.operatingSystem; + } else { + return null; + } + } + + Future _getUpdateInfoFromRemoteConfig() async { + final String rawData = await firebaseRepository.fetchUpdateData(); + + // checks if failed to fetch data from Firebase Database and return + if (rawData.isEmpty) { + return null; + } + + // checks if failed to get app build number and return + final num appBuildNumber = await packageInfoRepository.getAppBuildNumber(); + if (appBuildNumber <= 0) { + return null; + } + + final Map mapValues = json.decode(rawData); + + final String? platformName = _platformName(); + + if (platformName == null) { + return null; + } + + final num minVersion = mapValues[platformName]['version']['minimum']; + final num latestVersion = mapValues[platformName]['version']['latest']; + + final bool isMandatory = appBuildNumber < minVersion; + + final bool isOptional = + appBuildNumber < latestVersion && appBuildNumber > minVersion; + + // checks if no update available and return + if (!isMandatory && !isOptional) { + return null; + } + + return _createUpdate( + mapValues[platformName], + isOptional, + isOptional ? latestVersion : minVersion, + ); + } + + Future _getUpdateInfoFromDatabase() async { + final dynamic rawData = await firebaseRepository.fetchUpdateData(); // checks if failed to fetch data from Firebase Database and return if (rawData == null) { @@ -82,16 +138,22 @@ class UpdateBloc extends Bloc { bool isOptional, num buildNumber, ) { - final String? rawChangelogs = rawUpdateInfo['changelogs']?.toString(); - final Map? changelogs = rawChangelogs != null - ? json.decode( - rawChangelogs.toString().substring(1, rawChangelogs.length - 1), - ) - : null; + final Map? changelogs; + if (firebaseRepository is FirebaseRemoteConfigRepository) { + changelogs = rawUpdateInfo['changelogs']; + } else { + final String? rawChangelogs = rawUpdateInfo['changelogs']?.toString(); + changelogs = rawChangelogs != null + ? json.decode( + rawChangelogs.toString().substring(1, rawChangelogs.length - 1), + ) + : null; + } return UpdateInfo( buildNumber: buildNumber, url: rawUpdateInfo['url'], + huaweiUrl: rawUpdateInfo['huawei_url'], changelog: decodeBase64(rawUpdateInfo['changelog'] ?? ''), changelogs: changelogs, isOptional: isOptional, diff --git a/packages/update_checker/lib/src/models/update_info.dart b/packages/update_checker/lib/src/models/update_info.dart index 927135868..cd4b6f2b4 100644 --- a/packages/update_checker/lib/src/models/update_info.dart +++ b/packages/update_checker/lib/src/models/update_info.dart @@ -10,6 +10,7 @@ class UpdateInfo extends Equatable { this.changelog, this.changelogs, this.url, + this.huaweiUrl, }); /// [isOptional] determines if the update is optional or not. @@ -33,6 +34,9 @@ class UpdateInfo extends Equatable { /// [url] is alternative url for updating the app, can be empty. final String? url; + /// [huaweiUrl] is Huawei URL for Redirecting Huawei Users to AppGallery. + final String? huaweiUrl; + @override List get props => [isOptional, buildNumber, url]; @@ -41,5 +45,6 @@ class UpdateInfo extends Equatable { 'isOptional: $isOptional, ' 'buildNumber: $buildNumber, ' 'url: $url' + 'huaweiUrl: $huaweiUrl' ')'; } diff --git a/packages/update_checker/lib/src/presentation/update_checker.dart b/packages/update_checker/lib/src/presentation/update_checker.dart index 38659152b..88eeaa134 100644 --- a/packages/update_checker/lib/src/presentation/update_checker.dart +++ b/packages/update_checker/lib/src/presentation/update_checker.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:update_checker/src/repositories/base_firebase.dart'; import '../../update_checker.dart'; @@ -9,6 +10,7 @@ class UpdateChecker extends StatefulWidget { /// Checks for update availability as soon as renders and will call the proper /// callback based on the UpdateState. const UpdateChecker({ + required this.firebaseRepository, this.child, this.onAvailable, this.onNotAvailable, @@ -18,6 +20,10 @@ class UpdateChecker extends StatefulWidget { /// The [child] that UpdateChecker widget will wrap. final Widget? child; + /// The [firebaseRepository] that fetch the update information + /// from the firebase system. + final BaseFirebase firebaseRepository; + /// [onAvailable] will be called when there is an update available. final Function(UpdateInfo)? onAvailable; @@ -32,11 +38,12 @@ class UpdateChecker extends StatefulWidget { } class _UpdateCheckerState extends State { - final UpdateBloc _updateBloc = UpdateBloc(); + late final UpdateBloc _updateBloc; @override void initState() { super.initState(); + _updateBloc = UpdateBloc(firebaseRepository: widget.firebaseRepository); _updateBloc.add(UpdateFetchEvent()); } diff --git a/packages/update_checker/lib/src/repositories/base_firebase.dart b/packages/update_checker/lib/src/repositories/base_firebase.dart new file mode 100644 index 000000000..cca5598f7 --- /dev/null +++ b/packages/update_checker/lib/src/repositories/base_firebase.dart @@ -0,0 +1,6 @@ +/// Firebase base repository will help to fetch the update information from +/// the firebase system. +abstract class BaseFirebase { + /// Fetches the update information from the firebase system. + Future fetchUpdateData(); +} diff --git a/packages/update_checker/lib/src/repositories/firebase_database.dart b/packages/update_checker/lib/src/repositories/firebase_database.dart index 2838984b4..67e42aa93 100644 --- a/packages/update_checker/lib/src/repositories/firebase_database.dart +++ b/packages/update_checker/lib/src/repositories/firebase_database.dart @@ -1,14 +1,16 @@ import 'dart:io'; import 'package:firebase_database/firebase_database.dart'; +import 'package:update_checker/src/repositories/base_firebase.dart'; /// Firebase Database repository will help to fetch the update information from /// the firebase database. -class FirebaseDatabaseRepository { +class FirebaseDatabaseRepository implements BaseFirebase{ /// Initializes the Firebase Database repository const FirebaseDatabaseRepository(); /// Fetches the update information from the database. + @override Future fetchUpdateData() async { final DatabaseReference dbRef = FirebaseDatabase.instance .ref() diff --git a/packages/update_checker/lib/src/repositories/firebase_remote_config.dart b/packages/update_checker/lib/src/repositories/firebase_remote_config.dart new file mode 100644 index 000000000..8fdd42e2d --- /dev/null +++ b/packages/update_checker/lib/src/repositories/firebase_remote_config.dart @@ -0,0 +1,20 @@ +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:update_checker/src/repositories/base_firebase.dart'; + +/// Firebase Remote Config repository will help to fetch the update information from +/// the firebase database. +class FirebaseRemoteConfigRepository implements BaseFirebase { + /// Initializes the Firebase Database repository + const FirebaseRemoteConfigRepository(); + + static const String _versionControlKey = 'app_version_control'; + + /// Fetches the update information from the database. + @override + Future fetchUpdateData() async { + await FirebaseRemoteConfig.instance.fetchAndActivate(); + final RemoteConfigValue remoteConfigValue = + FirebaseRemoteConfig.instance.getValue(_versionControlKey); + return remoteConfigValue.asString(); + } +} diff --git a/packages/update_checker/lib/src/repositories/repositories.dart b/packages/update_checker/lib/src/repositories/repositories.dart index 5c648fda1..58e2dc7f6 100644 --- a/packages/update_checker/lib/src/repositories/repositories.dart +++ b/packages/update_checker/lib/src/repositories/repositories.dart @@ -1,2 +1,3 @@ export 'firebase_database.dart'; export 'package_info.dart'; +export 'firebase_remote_config.dart'; diff --git a/packages/update_checker/pubspec.lock b/packages/update_checker/pubspec.lock index 0d7ce4b5e..6a9f8499d 100644 --- a/packages/update_checker/pubspec.lock +++ b/packages/update_checker/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: f175bc1414e4edf8c5b83372c98eeabecf8353f39c9da423c2cfdf1f1f508788 + sha256: eb0ac20f704799b986049fbb3c1c16421eca319a1b872378d669513e12452ba5 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.3.14" analyzer: dependency: transitive description: @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -157,50 +157,74 @@ packages: dependency: transitive description: name: firebase_core - sha256: ed611fb8e67e43ecc7956f242cecca383d87cf71aace27287aa5dd4bdba4ac07 + sha256: d301561d614487688d797717bef013a264c517d1d09e4c5c1325c3a64c835efb url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.24.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: "0df0a064ab0cad7f8836291ca6f3272edd7b83ad5b3540478ee46a0849d8022b" + sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 url: "https://pub.dev" source: hosted - version: "4.6.0" + version: "5.0.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: "347351a8f0518f3343d79a9a0690fa67ad232fc32e2ea270677791949eac792b" + sha256: "10159d9ee42c79f4548971d92f3f0fcd5791f6738cda3583a4e3b2c8b244c018" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.9.0" firebase_database: dependency: "direct main" description: name: firebase_database - sha256: d437ccb3537485cbcf92df60c493b1601e93bce579518a9874c8d7be5408231e + sha256: "373b54cc0a890f4f1547380d31232a718aa5e4ded1a8819e6c4709d7df8286e6" url: "https://pub.dev" source: hosted - version: "10.1.0" + version: "10.3.6" firebase_database_platform_interface: dependency: transitive description: name: firebase_database_platform_interface - sha256: "509351811777e8bcd643f295ae096d8e7741d9bf845467d3626e086d1ac89848" + sha256: "2e3edb4552848585aa0031c0d493699305b40da756ebe507686ca6bbe96369eb" url: "https://pub.dev" source: hosted - version: "0.2.3" + version: "0.2.5+14" firebase_database_web: dependency: transitive description: name: firebase_database_web - sha256: d4a319e7923380f6adc7a46de331ce372aeafe101ae0ec684fef0540d41821bb + sha256: "55ec085db984291668c232d8760b82c86c33f5e4a459721a2df0438c2ece5859" + url: "https://pub.dev" + source: hosted + version: "0.2.3+14" + firebase_remote_config: + dependency: "direct main" + description: + name: firebase_remote_config + sha256: "5266a5ba1fed89517b2e9db3728decb3ff3a1a695d417449381fa9a2a404850c" + url: "https://pub.dev" + source: hosted + version: "4.3.6" + firebase_remote_config_platform_interface: + dependency: transitive + description: + name: firebase_remote_config_platform_interface + sha256: "28b531ade258dbb6084fcf4905cef7f0e9c83574cb18e5010809a7016e157990" url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "1.4.14" + firebase_remote_config_web: + dependency: transitive + description: + name: firebase_remote_config_web + sha256: f11eaf48a081df69b753dafb969b0548d37881d4c4548dea3d12624cb338f44e + url: "https://pub.dev" + source: hosted + version: "1.4.14" flutter: dependency: "direct main" description: flutter @@ -292,18 +316,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -465,10 +489,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -505,26 +529,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" url: "https://pub.dev" source: hosted - version: "1.24.1" + version: "1.24.3" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" test_core: dependency: transitive description: name: test_core - sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.5.3" typed_data: dependency: transitive description: @@ -557,6 +581,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -590,5 +622,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.2" diff --git a/packages/update_checker/pubspec.yaml b/packages/update_checker/pubspec.yaml index 9577e8206..383a18a47 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.1.0 +version: 1.2.0 homepage: https://deriv.com/ publish_to: "none" @@ -17,6 +17,7 @@ dependencies: sdk: flutter flutter_bloc: ^8.1.2 package_info_plus: ^3.0.3 + firebase_remote_config: ^4.3.6 dev_dependencies: bloc_test: ^9.1.1 diff --git a/packages/update_checker/test/update_checker_test.dart b/packages/update_checker/test/update_checker_test.dart index 4df3734c6..c6b7ba7c0 100644 --- a/packages/update_checker/test/update_checker_test.dart +++ b/packages/update_checker/test/update_checker_test.dart @@ -38,7 +38,7 @@ void main() { when(() => mockPackageInfoRepository.getAppBuildNumber()) .thenAnswer((_) async => buildNumber); return UpdateBloc( - firebaseDatabaseRepository: mockFirebaseDatabaseRepository, + firebaseRepository: mockFirebaseDatabaseRepository, packageInfoRepository: mockPackageInfoRepository, ); }, @@ -48,7 +48,10 @@ void main() { test( 'should emit UpdateInitialState as initial state', - () => expect(UpdateBloc().state, UpdateInitialState()), + () => expect( + UpdateBloc(firebaseRepository: mockFirebaseDatabaseRepository) + .state, + UpdateInitialState()), ); generateBlocTest( diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 000000000..5a8d1a5b1 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,317 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + ansi_styles: + dependency: transitive + description: + name: ansi_styles + sha256: "9c656cc12b3c27b17dd982b2cc5c0cfdfbdabd7bc8f3ae5e8542d9867b47ce8a" + url: "https://pub.dev" + source: hosted + version: "0.3.2+1" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" + source: hosted + version: "1.3.1" + cli_launcher: + dependency: transitive + description: + name: cli_launcher + sha256: "5e7e0282b79e8642edd6510ee468ae2976d847a0a29b3916e85f5fa1bfe24005" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 + url: "https://pub.dev" + source: hosted + version: "0.4.0" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + conventional_commit: + dependency: transitive + description: + name: conventional_commit + sha256: dec15ad1118f029c618651a4359eb9135d8b88f761aa24e4016d061cd45948f2 + url: "https://pub.dev" + source: hosted + version: "0.6.0+1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + melos: + dependency: "direct dev" + description: + name: melos + sha256: a45e54b72cc2444b46be9d32a590119b9ba8c4e87117f2743a73ec049542f2d3 + url: "https://pub.dev" + source: hosted + version: "3.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + mustache_template: + dependency: transitive + description: + name: mustache_template + sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c + url: "https://pub.dev" + source: hosted + version: "2.0.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + platform: + dependency: transitive + description: + name: platform + sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" + prompts: + dependency: transitive + description: + name: prompts + sha256: "3773b845e85a849f01e793c4fc18a45d52d7783b4cb6c0569fad19f9d0a774a1" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pub_updater: + dependency: transitive + description: + name: pub_updater + sha256: b06600619c8c219065a548f8f7c192b3e080beff95488ed692780f48f69c0625 + url: "https://pub.dev" + source: hosted + version: "0.3.1" + pubspec: + dependency: transitive + description: + name: pubspec + sha256: f534a50a2b4d48dc3bc0ec147c8bd7c304280fff23b153f3f11803c4d49d927e + url: "https://pub.dev" + source: hosted + version: "2.3.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + url: "https://pub.dev" + source: hosted + version: "3.2.1" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uri: + dependency: transitive + description: + name: uri + sha256: "889eea21e953187c6099802b7b4cf5219ba8f3518f604a1033064d45b1b8268a" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: "1579d4a0340a83cf9e4d580ea51a16329c916973bffd5bd4b45e911b25d46bfd" + url: "https://pub.dev" + source: hosted + version: "2.1.1" +sdks: + dart: ">=3.0.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 000000000..1e42b50bb --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,7 @@ +name: deriv_packages_workspace + +environment: + sdk: ">=2.18.0 <4.0.0" + +dev_dependencies: + melos: 3.2.0 From d058855dcc88cd8d87ecf37b6964827cf5583386 Mon Sep 17 00:00:00 2001 From: Ahrar <98078754+ahrar-deriv@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:21:11 +0800 Subject: [PATCH 20/21] ci: merge dev to master and ci update (#364) * deps: update deriv_auth_ui dependencies * deps: update deriv_theme in deriv_ui * wip: auth-ui-update * added invalidresidence errror mapping for social login * feat: add deriv auth error state handler * feat: add auth error state handler in sign up page * feat: add auth error state handler in set password page * refactor: auth state handling in signup and set password page * feat: add social button flag * feat: country consent added in country_selection_layout * deps: remove relative path * refactor: auth state handler * test: country selection cubit test * test: auth state handler * feat: add gesture detector in get started layout * feat: reset pass success page * docs: update documentation based on latest changes * refactor: add eof and remove deprecated lint * refactor: make countries non-nullable * refactor: deriv country selection cubit * refactor: deriv country selection cubit * ci: integrate github actions and setup melos (#328) * chore: rename `AuthStateListener` * fix: change color to general * fix: reset password button alignment * fix: add missing ok label in referal dialog * feat: add flag for social login * fix: spacing after social flag * chore: add auth ui regex to deriv ui * feat: [MOBC-608] auth ui setting page (#320) * feat: setting page * fix: depricated issues * fix: change package_info_plus to match packages * fix: future void callback * refactor: move regex_helper func to deriv_ui * fix: remove sahani repo from yaml files * fix: deriv_auth_ui pubspec * fix: remove appEnv related to deriv ez as deriv ez is removed from production we dont need to include it in this package anymore * fix: revert back the appEnv and change the comment * chore: merge master to dev (#334) * add update_checker package * add Readme * add comments * update documentation * apply suggestions from PR review * update deps * update flutter_bloc package * Revert "update flutter_bloc package" This reverts commit 7188a184363fafe0601f0fa904a3f42937e40978. * Update packages/update_checker/lib/update_info.dart Co-authored-by: abed-binary <61964755+abed-binary@users.noreply.github.com> * update bloc library to 6.0.1 * remove markseen * remove unnecessary statements * restore UpdateCheckMarkSeen * add UpdateCheckDismiss * push update_checker changes * Update web_socket_test.dart * chore: add pull request template with git rules * chore: pull request update git_rules.md --------- Co-authored-by: Suyash Mohan Co-authored-by: Suyash Mohan <33442325+suyash-binary@users.noreply.github.com> Co-authored-by: abed-binary <61964755+abed-binary@users.noreply.github.com> Co-authored-by: Morteza Tavanarad Co-authored-by: ramin-deriv Co-authored-by: ramin-deriv <55975218+ramin-deriv@users.noreply.github.com> Co-authored-by: WAQAS YOUNAS Co-authored-by: WAQAS YOUNAS <78855520+waqas-younas-deriv@users.noreply.github.com> * add invalidate and copyValuesFrom to AbstractEMAIndicator (#331) * ci: automated versioning github actions and updated ref (#338) * deps: update dependencies update dependencies deriv_ui update dependencies part 1 update dependencies deriv_auth_ui * deps: push melos pubspec.lock * chore: publish packages chore(release): publish packages - analytics@1.0.0 - deriv_app_performance@0.0.1 - flutter_deriv_bloc_manager@0.0.1 - deriv_datadog@0.0.1 - deriv_dependency_injector@1.0.0 - deriv_env@0.0.1 - deriv_grouped_listview@0.0.1 - deriv_http_client@1.0.0 - deriv_lint@1.0.0 - deriv_rudderstack@1.1.0 - deriv_technical_analysis@0.0.1 - deriv_theme@2.0.0 - deriv_web_socket_client@1.0.0 - form_builder@1.0.0+1 - update_checker@1.1.0 chore(release): publish packages - deriv_auth@1.0.2 - deriv_banner@0.0.1 - deriv_date_range_picker@0.0.1 - deriv_expandable_bottom_sheet@0.0.1 - deriv_live_chat@0.0.1 - deriv_numpad@1.0.0+1 - deriv_store_launcher@0.0.1 - deriv_web_view@0.0.1 chore(release): publish packages - deriv_ui@0.0.1 chore(release): publish packages - deriv_auth_ui@0.0.1 * chore: clean changelog * chore: revert changelog changes * chore: change auth ui example app ref * ci: git actions for versioning * refactor(deriv_env): make package independent of env file (#318) * get the env file content from outside * make Env class not being singleton * create EnvLoader class * fix the tests * bring back the removed test case * update the initialize doc * export classes need to be used by outside * export BaseEnv class as well * format deriv_env.dart * swape names for Env and EnvLoader to reduce the amount of change in the apps using this packagew * handle exception in bool type * remove duplicate lint rules * define common values as variable in env_test.dart * update README.md file * remove breaking change by making the laod method deprecated * Update CHANGELOG.md * udpate README.md file * refactor: Improve flexibility by removing dependency to env file - **Deprecation:** The `load` method is deprecated; it is recommended to use the new `initialize` method for improved flexibility. The `load` method will still function but may be removed in future releases. - Introduced a new method `initialize` to handle initialization without requiring an env file directly. - Improved package flexibility by removing the strict dependency on an env file for test environments. * chore: [MOBC-632] remove commit hash ci (#340) * chore(version): bump version and update changelog (#342) - deriv_env@0.0.1+1 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * chore(deriv_auth_ui): remove unecessary deps from example (#343) * feat: Ayaan/Added margins 112 and 164 (#319) * Add dimens 112 and 164 * Update dimens.dart * chore(release): publish packages (#344) - deriv_theme@2.1.0 - deriv_date_range_picker@0.0.1+1 - deriv_ui@0.0.1+1 - deriv_auth_ui@0.0.1+1 - deriv_expandable_bottom_sheet@0.0.1+1 - deriv_numpad@1.0.1 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * refactor(deriv_auth_ui): [MOBC-629] Adding semantics to UI components (#321) * chore(deriv_auth_ui):Adding semantics to UI components * chore(deriv_auth_ui):Adding semantics to UI components, fixes based on review comments * chore(deriv_auth_ui):Adding explicitChildNodes: true to Semantics Widgets * chore(release): publish packages (#345) - deriv_auth_ui@0.0.1+2 - deriv_ui@0.0.1+2 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * docs: add semantic versioning to git rules doc (#346) * docs: add semantic versioning document * docs: styling the git rules md file * docs: GIT_RULES style * chore: Update .github/GIT_RULES.md add refactor Co-authored-by: sahani-deriv <125638269+sahani-deriv@users.noreply.github.com> * chore: add missing format GIT_RULES.md --------- Co-authored-by: sahani-deriv <125638269+sahani-deriv@users.noreply.github.com> * feat(deriv_auth): [DERG-1299] add user agent to login (#341) * feat(deriv_auth): add user agent add optional user agent for login requests * feat(deriv_auth)add user agent to get user tokens request header * fix(deriv_auth): User-Agent typo * fix(deriv_auth): add missing parameter * fix(deriv_auth): add default value for user agent parameter --------- Co-authored-by: Ahrar * chore(release): publish packages (#348) - deriv_auth@1.1.0 - deriv_auth_ui@0.0.1+3 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * add remote config source to update checker * update key * update version rename base firebase class checking platform type * fixing decode changelogs data. * fixing change logs issue * fixing change logs issue * add fetchAndActivate to remote config * add fetchAndActivate to remote config * add fetchAndActivate to remote config * add huaweiUrl to the UpdateInfo model to redirecting users to app gallery. * fix comments * fix comments * chore:bum up version (#351) * refactor(analytics): [MOBC-546] Creating unified analytics package. (#315) * Refactoring `analytics` package. * PR review comments on `analytics` package * added `setUserProperty` function to `DerivFirebaseAnalytics` * fixed a test case * EOF `.env.example` in `analytics` package * chore(analytics): fixed package version. * chore(analytics): added `logEvent` function to DerivDatadog * docs(analytics): corrected `setup` function call for datadog * chore(analytics): added `.env` file to `.gitignore` * refactor(analytics): added `debugEnabled` to `RudderstackConfiguration` * docs(analytics): added debugEnabled to RudderstackConfiguration * revert(analytics): versioning and CHANGELOG - Introduced a flexible configuration system to support multiple analytics providers. - Added `BaseAnalyticsConfiguration` interface for standardizing analytics configuration. - Internalized and restructured Datadog analytics by providing a `DerivDatadog` class that implements `BaseAnalytics`. - Refined existing analytics services to have their own classes implementing `BaseAnalytics`. - Introduced Enum types and extensions (`TrackingConsent`, `DatadogSite`) to aid in analytics configuration. - Improved navigation observer support to work with multiple analytics providers. - Enhanced exception handling to capture Flutter and Platform-specific errors. - Added ability to set user information for analytics tracking. - Replaced in-house Rudderstack plugin with the official Rudderstack SDK. ### Breaking changes: **General**: - `setup()` method for analytics providers now requires configuration classes implementing `BaseAnalyticsConfiguration`. - Exception handling updated to include multiple analytics providers. - Rudderstack: Now requires the official Rudderstack SDK for analytics. Update dependencies accordingly. - Datadog: Migrated from a separate package to an internal implementation within this package. - Existing analytics services have been restructured to implement `BaseAnalytics`, potentially changing function signatures and configurations. * chore(release): publish packages (#356) - analytics@1.0.1 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * ci: replace tag with commit hash on third-party actions (#350) * refactor(deriv_dependency_injector): [MOBC-534] deprecate deriv dependency injector (#347) * chore(release): publish packages (#362) - deriv_dependency_injector@1.0.1 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> * ci: change version.yaml to point to master * chore: version.yaml eof removed --------- Co-authored-by: sahani-deriv <125638269+sahani-deriv@users.noreply.github.com> Co-authored-by: Istiak Co-authored-by: Suyash Mohan Co-authored-by: Suyash Mohan <33442325+suyash-binary@users.noreply.github.com> Co-authored-by: abed-binary <61964755+abed-binary@users.noreply.github.com> Co-authored-by: Morteza Tavanarad Co-authored-by: ramin-deriv Co-authored-by: ramin-deriv <55975218+ramin-deriv@users.noreply.github.com> Co-authored-by: WAQAS YOUNAS Co-authored-by: WAQAS YOUNAS <78855520+waqas-younas-deriv@users.noreply.github.com> Co-authored-by: mobile-apps-deriv <134251399+mobile-apps-deriv@users.noreply.github.com> Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: ayaan-deriv <142789997+ayaan-deriv@users.noreply.github.com> Co-authored-by: ernest-deriv <120568427+ernest-deriv@users.noreply.github.com> Co-authored-by: zohreh <108648307+zohreh-deriv@users.noreply.github.com> Co-authored-by: Reza <94842463+Reza-deriv@users.noreply.github.com> Co-authored-by: Bassam El Obeid <127500305+bassam-deriv@users.noreply.github.com> Co-authored-by: Bassam El Obeid --- .github/workflows/all_packages.yaml | 8 +++---- .github/workflows/pr_title.yaml | 2 +- .github/workflows/version.yaml | 21 +++++++------------ CHANGELOG.md | 21 +++++++++++++++++++ .../deriv_dependency_injector/CHANGELOG.md | 4 ++++ .../lib/src/injector.dart | 4 ++++ .../deriv_dependency_injector/pubspec.yaml | 2 +- 7 files changed, 42 insertions(+), 20 deletions(-) diff --git a/.github/workflows/all_packages.yaml b/.github/workflows/all_packages.yaml index 6e25797c2..fb3c00d1d 100644 --- a/.github/workflows/all_packages.yaml +++ b/.github/workflows/all_packages.yaml @@ -18,22 +18,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Setup Flutter - uses: subosito/flutter-action@v2 + uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa with: channel: "stable" flutter-version: "3.10.2" cache: true - name: Set SSH Key - uses: webfactory/ssh-agent@v0.8.0 + uses: webfactory/ssh-agent@fd34b8dee206fe74b288a5e61bc95fba2f1911eb with: ssh-private-key: ${{secrets.SSH_PRIVATE_KEY}} - name: Install Melos and run pub get - uses: bluefireteam/melos-action@v1 + uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a with: melos-version: "3.0.1" diff --git a/.github/workflows/pr_title.yaml b/.github/workflows/pr_title.yaml index 731c27830..b40f4d73d 100644 --- a/.github/workflows/pr_title.yaml +++ b/.github/workflows/pr_title.yaml @@ -14,6 +14,6 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@v5 + - uses: amannn/action-semantic-pull-request@e9fabac35e210fea40ca5b14c0da95a099eff26f env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/version.yaml b/.github/workflows/version.yaml index c93910aab..f8ef36d59 100644 --- a/.github/workflows/version.yaml +++ b/.github/workflows/version.yaml @@ -5,11 +5,7 @@ on: types: - closed branches: - - dev - -permissions: - contents: write - pull-requests: write + - master jobs: version_and_tag: @@ -17,39 +13,36 @@ jobs: if: github.event.pull_request.merged == true && !contains(github.event.pull_request.title, 'chore(version)') steps: - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 with: ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} fetch-depth: 0 - name: Setup Git User - uses: fregante/setup-git-user@v2 + uses: fregante/setup-git-user@77c1b5542f14ab6db4b8462d6857e31deb988b09 - name: Setup Flutter - uses: subosito/flutter-action@v2 + uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa with: channel: "stable" flutter-version: "3.10.2" cache: true - name: Setup Melos - uses: bluefireteam/melos-action@v3 + uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a with: melos-version: "3.2.0" run-bootstrap: false - name: Create git tag based on version - # only consider changes from the commit mentioned below run: melos version --all --yes - name: Push tag run: git push --tags - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create Pull Request on updated changelog and pubspec file. - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@76c6f5c20e2111bfee3cd30fae52a25e410f5efc with: token: ${{ secrets.PAT }} title: "chore(version): bump version and update changelog" - base: dev + base: master diff --git a/CHANGELOG.md b/CHANGELOG.md index 5999f79a0..e9b5b6acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2023-12-12 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_dependency_injector` - `v1.0.1`](#deriv_dependency_injector---v101) + +--- + +#### `deriv_dependency_injector` - `v1.0.1` + + - **REFACTOR**(deriv_dependency_injector): [MOBC-534] deprecate deriv dependency injector ([#347](https://github.com/regentmarkets/flutter-deriv-packages/issues/347)). ([92426f15](https://github.com/regentmarkets/flutter-deriv-packages/commit/92426f15eaa5caa529724590e006fd0f65d6800e)) + + ## 2023-12-08 ### Changes diff --git a/packages/deriv_dependency_injector/CHANGELOG.md b/packages/deriv_dependency_injector/CHANGELOG.md index effe43c82..87ea0302b 100644 --- a/packages/deriv_dependency_injector/CHANGELOG.md +++ b/packages/deriv_dependency_injector/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.1 + + - **REFACTOR**(deriv_dependency_injector): [MOBC-534] deprecate deriv dependency injector ([#347](https://github.com/regentmarkets/flutter-deriv-packages/issues/347)). ([92426f15](https://github.com/regentmarkets/flutter-deriv-packages/commit/92426f15eaa5caa529724590e006fd0f65d6800e)) + ## 1.0.0 - Initial version. diff --git a/packages/deriv_dependency_injector/lib/src/injector.dart b/packages/deriv_dependency_injector/lib/src/injector.dart index c7b5bb832..fbc98ff08 100644 --- a/packages/deriv_dependency_injector/lib/src/injector.dart +++ b/packages/deriv_dependency_injector/lib/src/injector.dart @@ -2,6 +2,10 @@ import 'package:deriv_dependency_injector/dependency_injector.dart'; const String _defaultKey = 'default'; +@Deprecated('This package is deprecated and will be removed in the future. ' + 'Please use the deriv dependency injector package from: ' + 'https://github.com/deriv-com/deriv-dependency-injector') + /// Injector class for dependency injection. class Injector { /// Get the instance of the named injector or create a new one if it doesn't exist. diff --git a/packages/deriv_dependency_injector/pubspec.yaml b/packages/deriv_dependency_injector/pubspec.yaml index 0101e4b74..b32f63df9 100644 --- a/packages/deriv_dependency_injector/pubspec.yaml +++ b/packages/deriv_dependency_injector/pubspec.yaml @@ -1,7 +1,7 @@ name: deriv_dependency_injector description: A package for handling dependency injection in Dart. -version: 1.0.0 +version: 1.0.1 environment: sdk: ">=3.0.0 <4.0.0" From 7cc0f14126c48ff840e29b6ebeb3c48696094074 Mon Sep 17 00:00:00 2001 From: mobile-apps-deriv <134251399+mobile-apps-deriv@users.noreply.github.com> Date: Thu, 14 Dec 2023 12:03:49 +0800 Subject: [PATCH 21/21] chore(release): publish packages (#365) - analytics@1.0.2 - deriv_auth@1.2.0 - deriv_auth_ui@0.0.2 - deriv_date_range_picker@0.0.1+2 - deriv_dependency_injector@1.0.2 - deriv_env@0.0.1+2 - deriv_numpad@1.0.2 - deriv_theme@2.2.0 - deriv_ui@0.0.2 - deriv_expandable_bottom_sheet@0.0.1+2 Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 91 +++++++++++++++++++ packages/analytics/CHANGELOG.md | 5 + packages/analytics/pubspec.yaml | 2 +- packages/deriv_auth/CHANGELOG.md | 4 + packages/deriv_auth/pubspec.yaml | 2 +- packages/deriv_auth_ui/CHANGELOG.md | 24 +++++ packages/deriv_auth_ui/pubspec.yaml | 8 +- packages/deriv_date_range_picker/CHANGELOG.md | 4 + packages/deriv_date_range_picker/pubspec.yaml | 4 +- .../deriv_dependency_injector/CHANGELOG.md | 4 + .../deriv_dependency_injector/pubspec.yaml | 2 +- packages/deriv_env/CHANGELOG.md | 4 + packages/deriv_env/pubspec.yaml | 2 +- .../CHANGELOG.md | 4 + .../pubspec.yaml | 4 +- packages/deriv_numpad/CHANGELOG.md | 4 + packages/deriv_numpad/pubspec.yaml | 4 +- packages/deriv_theme/CHANGELOG.md | 4 + packages/deriv_theme/pubspec.yaml | 2 +- packages/deriv_ui/CHANGELOG.md | 6 ++ packages/deriv_ui/pubspec.yaml | 4 +- 21 files changed, 171 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9b5b6acc..39c41288e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,97 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2023-12-13 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`analytics` - `v1.0.2`](#analytics---v102) + - [`deriv_auth` - `v1.2.0`](#deriv_auth---v120) + - [`deriv_auth_ui` - `v0.0.2`](#deriv_auth_ui---v002) + - [`deriv_date_range_picker` - `v0.0.1+2`](#deriv_date_range_picker---v0012) + - [`deriv_dependency_injector` - `v1.0.2`](#deriv_dependency_injector---v102) + - [`deriv_env` - `v0.0.1+2`](#deriv_env---v0012) + - [`deriv_numpad` - `v1.0.2`](#deriv_numpad---v102) + - [`deriv_theme` - `v2.2.0`](#deriv_theme---v220) + - [`deriv_ui` - `v0.0.2`](#deriv_ui---v002) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+2`](#deriv_expandable_bottom_sheet---v0012) + +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_expandable_bottom_sheet` - `v0.0.1+2` + +--- + +#### `analytics` - `v1.0.2` + + - **REVERT**(analytics): versioning and CHANGELOG. ([81a3a0df](https://github.com/regentmarkets/flutter-deriv-packages/commit/81a3a0df27208bd200009415855c6cb944d016e3)) + - **REFACTOR**(analytics): [MOBC-546] Creating unified analytics package. ([#315](https://github.com/regentmarkets/flutter-deriv-packages/issues/315)). ([fd1d8ed3](https://github.com/regentmarkets/flutter-deriv-packages/commit/fd1d8ed345d4ecf91c5f6c1463c5196b40abcbf6)) + +#### `deriv_auth` - `v1.2.0` + + - **FEAT**(deriv_auth): [DERG-1299] add user agent to login ([#341](https://github.com/regentmarkets/flutter-deriv-packages/issues/341)). ([37f5b763](https://github.com/regentmarkets/flutter-deriv-packages/commit/37f5b763d8a679ff1d458fc4e9b82578f4eecf83)) + +#### `deriv_auth_ui` - `v0.0.2` + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + - **REFACTOR**: deriv country selection cubit. ([5b0d5f0c](https://github.com/regentmarkets/flutter-deriv-packages/commit/5b0d5f0c37ecd732739204d2a41c334779803945)) + - **REFACTOR**: deriv country selection cubit. ([e88f6332](https://github.com/regentmarkets/flutter-deriv-packages/commit/e88f6332844433953a63ff5933612ea2e115b045)) + - **REFACTOR**: make countries non-nullable. ([e65d7587](https://github.com/regentmarkets/flutter-deriv-packages/commit/e65d7587e334e82486f6510a50015784adf6fddd)) + - **REFACTOR**: add eof and remove deprecated lint. ([c2c7a80a](https://github.com/regentmarkets/flutter-deriv-packages/commit/c2c7a80a5fe39f747897a8a6be09701d125716e9)) + - **REFACTOR**: auth state handler. ([84e2099d](https://github.com/regentmarkets/flutter-deriv-packages/commit/84e2099d65abe8585c64773254fca99059b3b4cb)) + - **REFACTOR**: auth state handling in signup and set password page. ([05c12d53](https://github.com/regentmarkets/flutter-deriv-packages/commit/05c12d53906de229d316fdbcf18396e4b34aed1f)) + - **FIX**: spacing after social flag. ([d782c86a](https://github.com/regentmarkets/flutter-deriv-packages/commit/d782c86a69eae3489e261060c7eacf63eb2da9d0)) + - **FIX**: add missing ok label in referal dialog. ([086fc636](https://github.com/regentmarkets/flutter-deriv-packages/commit/086fc636bdae24da873a9931e7b053b50e6b1a73)) + - **FIX**: reset password button alignment. ([411b2115](https://github.com/regentmarkets/flutter-deriv-packages/commit/411b2115d0f891b61036e601c5f26045e3a861f3)) + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + - **FEAT**: [MOBC-608] auth ui setting page ([#320](https://github.com/regentmarkets/flutter-deriv-packages/issues/320)). ([ce8d202b](https://github.com/regentmarkets/flutter-deriv-packages/commit/ce8d202bc1636f29ca99475d645d99aead663e57)) + - **FEAT**: add flag for social login. ([016f3902](https://github.com/regentmarkets/flutter-deriv-packages/commit/016f3902d6d110f76446a0cc4ce0edfa2f35dd8a)) + - **FEAT**: reset pass success page. ([afb19891](https://github.com/regentmarkets/flutter-deriv-packages/commit/afb1989104a02fc3e8c03a55e1b35216500be422)) + - **FEAT**: add gesture detector in get started layout. ([185928c6](https://github.com/regentmarkets/flutter-deriv-packages/commit/185928c6d8489d8edf3732a58cf05d91b0cac71b)) + - **FEAT**: country consent added in country_selection_layout. ([7cc23945](https://github.com/regentmarkets/flutter-deriv-packages/commit/7cc239455f1169777f8c11d78487f1e39e35e76b)) + - **FEAT**: add social button flag. ([f8f95171](https://github.com/regentmarkets/flutter-deriv-packages/commit/f8f9517127a5cda26eed12c290f702c8623826e9)) + - **FEAT**: add auth error state handler in set password page. ([737ed84a](https://github.com/regentmarkets/flutter-deriv-packages/commit/737ed84a282869bce1bbde00794309e9e9fc2b34)) + - **FEAT**: add auth error state handler in sign up page. ([b7910157](https://github.com/regentmarkets/flutter-deriv-packages/commit/b79101574ac82790c8acb3fb5cda93dfb6274500)) + - **FEAT**: add deriv auth error state handler. ([6818ff32](https://github.com/regentmarkets/flutter-deriv-packages/commit/6818ff321b0875a039013e339e55cabba683a4fb)) + - **DOCS**: update documentation based on latest changes. ([4ba23ea9](https://github.com/regentmarkets/flutter-deriv-packages/commit/4ba23ea9b791d5d1d6aaf54fd4562ba1232e4259)) + +#### `deriv_date_range_picker` - `v0.0.1+2` + + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + +#### `deriv_dependency_injector` - `v1.0.2` + + - **REFACTOR**(deriv_dependency_injector): [MOBC-534] deprecate deriv dependency injector ([#347](https://github.com/regentmarkets/flutter-deriv-packages/issues/347)). ([92426f15](https://github.com/regentmarkets/flutter-deriv-packages/commit/92426f15eaa5caa529724590e006fd0f65d6800e)) + +#### `deriv_env` - `v0.0.1+2` + + - **REFACTOR**(deriv_env): make package independent of env file ([#318](https://github.com/regentmarkets/flutter-deriv-packages/issues/318)). ([a7242c81](https://github.com/regentmarkets/flutter-deriv-packages/commit/a7242c81b97fda70a622d7bbbb97fe997067117a)) + +#### `deriv_numpad` - `v1.0.2` + + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + +#### `deriv_theme` - `v2.2.0` + + - **FEAT**: Ayaan/Added margins 112 and 164 ([#319](https://github.com/regentmarkets/flutter-deriv-packages/issues/319)). ([bf625820](https://github.com/regentmarkets/flutter-deriv-packages/commit/bf6258206b4bb4cbdd1ef6744e07e2adb8d0d5ee)) + +#### `deriv_ui` - `v0.0.2` + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + - **FEAT**: country consent added in country_selection_layout. ([7cc23945](https://github.com/regentmarkets/flutter-deriv-packages/commit/7cc239455f1169777f8c11d78487f1e39e35e76b)) + + ## 2023-12-12 ### Changes diff --git a/packages/analytics/CHANGELOG.md b/packages/analytics/CHANGELOG.md index df6cadb3a..b928744a3 100644 --- a/packages/analytics/CHANGELOG.md +++ b/packages/analytics/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.0.2 + + - **REVERT**(analytics): versioning and CHANGELOG. ([81a3a0df](https://github.com/regentmarkets/flutter-deriv-packages/commit/81a3a0df27208bd200009415855c6cb944d016e3)) + - **REFACTOR**(analytics): [MOBC-546] Creating unified analytics package. ([#315](https://github.com/regentmarkets/flutter-deriv-packages/issues/315)). ([fd1d8ed3](https://github.com/regentmarkets/flutter-deriv-packages/commit/fd1d8ed345d4ecf91c5f6c1463c5196b40abcbf6)) + ## 1.0.1 - **REVERT**(analytics): versioning and CHANGELOG. ([81a3a0df](https://github.com/regentmarkets/flutter-deriv-packages/commit/81a3a0df27208bd200009415855c6cb944d016e3)) diff --git a/packages/analytics/pubspec.yaml b/packages/analytics/pubspec.yaml index 2aa153901..93035b79b 100644 --- a/packages/analytics/pubspec.yaml +++ b/packages/analytics/pubspec.yaml @@ -1,6 +1,6 @@ name: analytics description: A new Flutter package for collecting and sending analytical information from the app. -version: 1.0.1 +version: 1.0.2 homepage: https://deriv.com/ publish_to: "none" diff --git a/packages/deriv_auth/CHANGELOG.md b/packages/deriv_auth/CHANGELOG.md index 65e189307..25ae5da66 100644 --- a/packages/deriv_auth/CHANGELOG.md +++ b/packages/deriv_auth/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.2.0 + + - **FEAT**(deriv_auth): [DERG-1299] add user agent to login ([#341](https://github.com/regentmarkets/flutter-deriv-packages/issues/341)). ([37f5b763](https://github.com/regentmarkets/flutter-deriv-packages/commit/37f5b763d8a679ff1d458fc4e9b82578f4eecf83)) + ## 1.1.0 - **FEAT**(deriv_auth): [DERG-1299] add user agent to login ([#341](https://github.com/regentmarkets/flutter-deriv-packages/issues/341)). ([37f5b763](https://github.com/regentmarkets/flutter-deriv-packages/commit/37f5b763d8a679ff1d458fc4e9b82578f4eecf83)) diff --git a/packages/deriv_auth/pubspec.yaml b/packages/deriv_auth/pubspec.yaml index 8c8417d88..0522b2111 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: 1.1.0 +version: 1.2.0 publish_to: "none" diff --git a/packages/deriv_auth_ui/CHANGELOG.md b/packages/deriv_auth_ui/CHANGELOG.md index 9a9cea2da..1e011a8db 100644 --- a/packages/deriv_auth_ui/CHANGELOG.md +++ b/packages/deriv_auth_ui/CHANGELOG.md @@ -1,3 +1,27 @@ +## 0.0.2 + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + - **REFACTOR**: deriv country selection cubit. ([5b0d5f0c](https://github.com/regentmarkets/flutter-deriv-packages/commit/5b0d5f0c37ecd732739204d2a41c334779803945)) + - **REFACTOR**: deriv country selection cubit. ([e88f6332](https://github.com/regentmarkets/flutter-deriv-packages/commit/e88f6332844433953a63ff5933612ea2e115b045)) + - **REFACTOR**: make countries non-nullable. ([e65d7587](https://github.com/regentmarkets/flutter-deriv-packages/commit/e65d7587e334e82486f6510a50015784adf6fddd)) + - **REFACTOR**: add eof and remove deprecated lint. ([c2c7a80a](https://github.com/regentmarkets/flutter-deriv-packages/commit/c2c7a80a5fe39f747897a8a6be09701d125716e9)) + - **REFACTOR**: auth state handler. ([84e2099d](https://github.com/regentmarkets/flutter-deriv-packages/commit/84e2099d65abe8585c64773254fca99059b3b4cb)) + - **REFACTOR**: auth state handling in signup and set password page. ([05c12d53](https://github.com/regentmarkets/flutter-deriv-packages/commit/05c12d53906de229d316fdbcf18396e4b34aed1f)) + - **FIX**: spacing after social flag. ([d782c86a](https://github.com/regentmarkets/flutter-deriv-packages/commit/d782c86a69eae3489e261060c7eacf63eb2da9d0)) + - **FIX**: add missing ok label in referal dialog. ([086fc636](https://github.com/regentmarkets/flutter-deriv-packages/commit/086fc636bdae24da873a9931e7b053b50e6b1a73)) + - **FIX**: reset password button alignment. ([411b2115](https://github.com/regentmarkets/flutter-deriv-packages/commit/411b2115d0f891b61036e601c5f26045e3a861f3)) + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + - **FEAT**: [MOBC-608] auth ui setting page ([#320](https://github.com/regentmarkets/flutter-deriv-packages/issues/320)). ([ce8d202b](https://github.com/regentmarkets/flutter-deriv-packages/commit/ce8d202bc1636f29ca99475d645d99aead663e57)) + - **FEAT**: add flag for social login. ([016f3902](https://github.com/regentmarkets/flutter-deriv-packages/commit/016f3902d6d110f76446a0cc4ce0edfa2f35dd8a)) + - **FEAT**: reset pass success page. ([afb19891](https://github.com/regentmarkets/flutter-deriv-packages/commit/afb1989104a02fc3e8c03a55e1b35216500be422)) + - **FEAT**: add gesture detector in get started layout. ([185928c6](https://github.com/regentmarkets/flutter-deriv-packages/commit/185928c6d8489d8edf3732a58cf05d91b0cac71b)) + - **FEAT**: country consent added in country_selection_layout. ([7cc23945](https://github.com/regentmarkets/flutter-deriv-packages/commit/7cc239455f1169777f8c11d78487f1e39e35e76b)) + - **FEAT**: add social button flag. ([f8f95171](https://github.com/regentmarkets/flutter-deriv-packages/commit/f8f9517127a5cda26eed12c290f702c8623826e9)) + - **FEAT**: add auth error state handler in set password page. ([737ed84a](https://github.com/regentmarkets/flutter-deriv-packages/commit/737ed84a282869bce1bbde00794309e9e9fc2b34)) + - **FEAT**: add auth error state handler in sign up page. ([b7910157](https://github.com/regentmarkets/flutter-deriv-packages/commit/b79101574ac82790c8acb3fb5cda93dfb6274500)) + - **FEAT**: add deriv auth error state handler. ([6818ff32](https://github.com/regentmarkets/flutter-deriv-packages/commit/6818ff321b0875a039013e339e55cabba683a4fb)) + - **DOCS**: update documentation based on latest changes. ([4ba23ea9](https://github.com/regentmarkets/flutter-deriv-packages/commit/4ba23ea9b791d5d1d6aaf54fd4562ba1232e4259)) + ## 0.0.1+3 - Update a dependency to the latest release. diff --git a/packages/deriv_auth_ui/pubspec.yaml b/packages/deriv_auth_ui/pubspec.yaml index b90543d4c..585772346 100644 --- a/packages/deriv_auth_ui/pubspec.yaml +++ b/packages/deriv_auth_ui/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_auth_ui description: A flutter package for deriv auth UI flows. -version: 0.0.1+3 +version: 0.0.2 environment: sdk: ">=3.0.0 <4.0.0" @@ -18,17 +18,17 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_auth - ref: deriv_auth-v1.1.0 + ref: deriv_auth-v1.2.0 deriv_theme: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_theme - ref: deriv_theme-v2.1.0 + ref: deriv_theme-v2.2.0 deriv_ui: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_ui - ref: deriv_ui-v0.0.1+2 + ref: deriv_ui-v0.0.2 flutter_svg: ^1.1.6 smooth_page_indicator: ^1.1.0 intl_utils: ^2.8.3 diff --git a/packages/deriv_date_range_picker/CHANGELOG.md b/packages/deriv_date_range_picker/CHANGELOG.md index b7f2af439..b19b4b3c9 100644 --- a/packages/deriv_date_range_picker/CHANGELOG.md +++ b/packages/deriv_date_range_picker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.1+2 + + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + ## 0.0.1+1 - Update a dependency to the latest release. diff --git a/packages/deriv_date_range_picker/pubspec.yaml b/packages/deriv_date_range_picker/pubspec.yaml index a188da8a5..f0e791afe 100644 --- a/packages/deriv_date_range_picker/pubspec.yaml +++ b/packages/deriv_date_range_picker/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_date_range_picker description: A new Flutter package project. -version: 0.0.1+1 +version: 0.0.1+2 publish_to: "none" # Remove this line if you wish to publish to pub.dev environment: @@ -17,7 +17,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_theme - ref: deriv_theme-v2.1.0 + ref: deriv_theme-v2.2.0 intl: ^0.18.0 diff --git a/packages/deriv_dependency_injector/CHANGELOG.md b/packages/deriv_dependency_injector/CHANGELOG.md index 87ea0302b..4a61e482d 100644 --- a/packages/deriv_dependency_injector/CHANGELOG.md +++ b/packages/deriv_dependency_injector/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.2 + + - **REFACTOR**(deriv_dependency_injector): [MOBC-534] deprecate deriv dependency injector ([#347](https://github.com/regentmarkets/flutter-deriv-packages/issues/347)). ([92426f15](https://github.com/regentmarkets/flutter-deriv-packages/commit/92426f15eaa5caa529724590e006fd0f65d6800e)) + ## 1.0.1 - **REFACTOR**(deriv_dependency_injector): [MOBC-534] deprecate deriv dependency injector ([#347](https://github.com/regentmarkets/flutter-deriv-packages/issues/347)). ([92426f15](https://github.com/regentmarkets/flutter-deriv-packages/commit/92426f15eaa5caa529724590e006fd0f65d6800e)) diff --git a/packages/deriv_dependency_injector/pubspec.yaml b/packages/deriv_dependency_injector/pubspec.yaml index b32f63df9..03cdcebef 100644 --- a/packages/deriv_dependency_injector/pubspec.yaml +++ b/packages/deriv_dependency_injector/pubspec.yaml @@ -1,7 +1,7 @@ name: deriv_dependency_injector description: A package for handling dependency injection in Dart. -version: 1.0.1 +version: 1.0.2 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/deriv_env/CHANGELOG.md b/packages/deriv_env/CHANGELOG.md index ecd5433ba..ed1952296 100644 --- a/packages/deriv_env/CHANGELOG.md +++ b/packages/deriv_env/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.1+2 + + - **REFACTOR**(deriv_env): make package independent of env file ([#318](https://github.com/regentmarkets/flutter-deriv-packages/issues/318)). ([a7242c81](https://github.com/regentmarkets/flutter-deriv-packages/commit/a7242c81b97fda70a622d7bbbb97fe997067117a)) + ## 0.0.1+1 - **REFACTOR**(deriv_env): make package independent of env file ([#318](https://github.com/regentmarkets/flutter-deriv-packages/issues/318)). ([a7242c81](https://github.com/regentmarkets/flutter-deriv-packages/commit/a7242c81b97fda70a622d7bbbb97fe997067117a)) diff --git a/packages/deriv_env/pubspec.yaml b/packages/deriv_env/pubspec.yaml index b12aa29ec..a8469b290 100644 --- a/packages/deriv_env/pubspec.yaml +++ b/packages/deriv_env/pubspec.yaml @@ -1,7 +1,7 @@ name: deriv_env description: A package to load and store environment variables. -version: 0.0.1+1 +version: 0.0.1+2 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/deriv_expandable_bottom_sheet/CHANGELOG.md b/packages/deriv_expandable_bottom_sheet/CHANGELOG.md index b7f2af439..9165f2f57 100644 --- a/packages/deriv_expandable_bottom_sheet/CHANGELOG.md +++ b/packages/deriv_expandable_bottom_sheet/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.1+2 + + - Update a dependency to the latest release. + ## 0.0.1+1 - Update a dependency to the latest release. diff --git a/packages/deriv_expandable_bottom_sheet/pubspec.yaml b/packages/deriv_expandable_bottom_sheet/pubspec.yaml index 2b33fe075..21a7eb7d2 100644 --- a/packages/deriv_expandable_bottom_sheet/pubspec.yaml +++ b/packages/deriv_expandable_bottom_sheet/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_expandable_bottom_sheet description: A new Flutter package project. -version: 0.0.1+1 +version: 0.0.1+2 publish_to: none environment: @@ -15,7 +15,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_theme - ref: deriv_theme-v2.1.0 + ref: deriv_theme-v2.2.0 dev_dependencies: flutter_test: diff --git a/packages/deriv_numpad/CHANGELOG.md b/packages/deriv_numpad/CHANGELOG.md index e9bf0b29c..7f7488158 100644 --- a/packages/deriv_numpad/CHANGELOG.md +++ b/packages/deriv_numpad/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.2 + + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + ## 1.0.1 - Update a dependency to the latest release. diff --git a/packages/deriv_numpad/pubspec.yaml b/packages/deriv_numpad/pubspec.yaml index cdc86e058..c926d03d9 100644 --- a/packages/deriv_numpad/pubspec.yaml +++ b/packages/deriv_numpad/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_numpad description: A new Flutter project. -version: 1.0.1 +version: 1.0.2 publish_to: "none" environment: @@ -13,7 +13,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_theme - ref: deriv_theme-v2.1.0 + ref: deriv_theme-v2.2.0 flutter_svg: ^1.0.3 intl: ^0.17.0 diff --git a/packages/deriv_theme/CHANGELOG.md b/packages/deriv_theme/CHANGELOG.md index b92d4999e..4cf0a7c5d 100644 --- a/packages/deriv_theme/CHANGELOG.md +++ b/packages/deriv_theme/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.0 + + - **FEAT**: Ayaan/Added margins 112 and 164 ([#319](https://github.com/regentmarkets/flutter-deriv-packages/issues/319)). ([bf625820](https://github.com/regentmarkets/flutter-deriv-packages/commit/bf6258206b4bb4cbdd1ef6744e07e2adb8d0d5ee)) + ## 2.1.0 - **FEAT**: Ayaan/Added margins 112 and 164 ([#319](https://github.com/regentmarkets/flutter-deriv-packages/issues/319)). ([bf625820](https://github.com/regentmarkets/flutter-deriv-packages/commit/bf6258206b4bb4cbdd1ef6744e07e2adb8d0d5ee)) diff --git a/packages/deriv_theme/pubspec.yaml b/packages/deriv_theme/pubspec.yaml index 79813bdcf..6bf57ec62 100644 --- a/packages/deriv_theme/pubspec.yaml +++ b/packages/deriv_theme/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_theme description: A flutter package that contains the theme used by deriv products -version: 2.1.0 +version: 2.2.0 homepage: https://deriv.com/ publish_to: "none" diff --git a/packages/deriv_ui/CHANGELOG.md b/packages/deriv_ui/CHANGELOG.md index 2a58aaa09..784915ffb 100644 --- a/packages/deriv_ui/CHANGELOG.md +++ b/packages/deriv_ui/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.0.2 + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + - **FEAT**: country consent added in country_selection_layout. ([7cc23945](https://github.com/regentmarkets/flutter-deriv-packages/commit/7cc239455f1169777f8c11d78487f1e39e35e76b)) + ## 0.0.1+2 - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) diff --git a/packages/deriv_ui/pubspec.yaml b/packages/deriv_ui/pubspec.yaml index 8201c1b5a..4cbf38887 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.1+2 +version: 0.0.2 publish_to: none environment: @@ -15,7 +15,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_theme - ref: deriv_theme-v2.1.0 + ref: deriv_theme-v2.2.0 deriv_web_view: git: