diff --git a/.gitignore b/.gitignore index 03ba70f..c179e32 100644 --- a/.gitignore +++ b/.gitignore @@ -28,9 +28,16 @@ node_modules **/.dart_tool/ example/build/ package/build/ +package/proto/ +package/lib/proto/ .flutter-plugins* package/test/.env-test.json /example/ios/_GoogleService-Info.plist /build/ /example/lib/firebase_options.dart /example/ios/Podfile.lock + +# Lockfiles +pubspec.lock +example/pubspec.lock +package/pubspec.lock diff --git a/example/.env.example b/example/.env.example deleted file mode 100644 index 97200d5..0000000 --- a/example/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -API_KEY= -SECRET_KEY= diff --git a/example/google-services.json b/example/google-services.json new file mode 100644 index 0000000..1356d57 --- /dev/null +++ b/example/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "719280351201", + "project_id": "teste-dito-sdk", + "storage_bucket": "teste-dito-sdk.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:719280351201:android:9407ea403f310b34a854a0", + "android_client_info": { + "package_name": "com.example.flutter_sdk_test" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCSJRyVepSWw3QopOI1kdsOQ-snsVMrPGY" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/example/lib/app_form.dart b/example/lib/app_form.dart index 5384ce7..825d236 100644 --- a/example/lib/app_form.dart +++ b/example/lib/app_form.dart @@ -1,6 +1,4 @@ import 'package:dito_sdk/dito_sdk.dart'; -import 'package:dito_sdk/event/event_entity.dart'; -import 'package:dito_sdk/user/user_entity.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -23,41 +21,174 @@ class AppFormState extends State { String cpf = "33333333333"; String email = "teste.sdk-flutter@dito.com.br"; - identify() async { - final user = UserEntity( - userID: "e400c65b1800bee5bf546c5b7bd37cd4f7452bb8", + identify() { + return dito.user.identify( + userID: "1575213826e164f73d28c4ed1b5fabaad4bd4a13", cpf: cpf, - name: 'Teste SDK Flutter 33333333333', + name: 'Usuário SDK FLutter - Teste', email: email); + } - await dito.user.identify(user); - await dito.notification - .registryToken(await dito.notification.getFirebaseToken()); + login() { + return dito.user + .login(userID: '1575213826e164f73d28c4ed1b5fabaad4bd4a13'); } handleIdentify() async { if (_formKey.currentState!.validate()) { - await identify().then((response) => { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Usuário identificado')), - ) - }); + final bool response = await identify(); + + if (response) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Usuário identificado')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ocorreu um erro!'), + backgroundColor: Colors.redAccent), + ); + } + } + } + + handleLogin() async { + if (_formKey.currentState!.validate()) { + final bool response = await login(); + + if (response) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Usuário logado')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ocorreu um erro!'), + backgroundColor: Colors.redAccent), + ); + } } } - handleNotification() async { + handleGenericTrack() async { if (_formKey.currentState!.validate()) { - await dito.event.trackEvent(EventEntity(eventName: 'action-test')); + final bool response = await dito.event.track(action: 'action-test'); + + if (response) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Evento de notificação solicitado')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ocorreu um erro!'), + backgroundColor: Colors.redAccent), + ); + } + } + } + + handleNavigation() async { + if (_formKey.currentState!.validate()) { + final bool response = await dito.event.navigate(name: 'home'); + + if (response) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Evento de notificação solicitado')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ocorreu um erro!'), + backgroundColor: Colors.redAccent), + ); + } + } + } + handleClickNotification() async { + if (_formKey.currentState!.validate()) { + final bool response = await dito.notification.click( + notification: 'notification-sdk-test', + ); + + if (response) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Evento de notificação solicitado')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ocorreu um erro!'), + backgroundColor: Colors.redAccent), + ); + } + } + } + + handleReceivedNotification() async { + if (_formKey.currentState!.validate()) { + final bool response = await dito.notification.received( + notification: 'notification-sdk-test', + ); + + if (response) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Evento de notificação solicitado')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ocorreu um erro!'), + backgroundColor: Colors.redAccent), + ); + } + } + } + + handleDeleteToken() async { + final bool response = await dito.user.token.removeToken(dito.user.data.token); + if (response) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Evento de notificação solicitado')), ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ocorreu um erro!'), + backgroundColor: Colors.redAccent), + ); } } - handleDeleteToken() async { - await dito.notification - .removeToken(await dito.notification.getFirebaseToken()); + handleRegistryToken() async { + final bool response = await dito.user.token.registryToken(dito.user.data.token); + if (response) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Evento de notificação solicitado')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ocorreu um erro!'), + backgroundColor: Colors.redAccent), + ); + } + } + + handlePingToken() async { + final bool response = await dito.user.token.pingToken(dito.user.data.token); + if (response) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Evento de notificação solicitado')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ocorreu um erro!'), + backgroundColor: Colors.redAccent), + ); + } } return Form( @@ -95,16 +226,40 @@ class AppFormState extends State { child: Column(children: [ FilledButton( onPressed: handleIdentify, - child: const Text('Registrar Identify'), + child: const Text('Alterar cadastro'), + ), + FilledButton( + onPressed: handleLogin, + child: const Text('Logar usuário'), + ), + OutlinedButton( + onPressed: handleGenericTrack, + child: const Text('Registrar evento genérico'), + ), + OutlinedButton( + onPressed: handleNavigation, + child: const Text('Registrar evento de navegação'), ), OutlinedButton( - onPressed: handleNotification, - child: const Text('Receber Notification'), + onPressed: handleClickNotification, + child: const Text('Registrar evento de click'), ), OutlinedButton( + onPressed: handleReceivedNotification, + child: const Text('Registrar evento de Entrega'), + ), + FilledButton( + onPressed: handleRegistryToken, + child: const Text('Registrar token'), + ), + FilledButton( onPressed: handleDeleteToken, child: const Text('Deletar token'), ), + FilledButton( + onPressed: handlePingToken, + child: const Text('Validar token'), + ), ]))) ], ), diff --git a/example/lib/constants.dart b/example/lib/constants.dart deleted file mode 100644 index 8871074..0000000 --- a/example/lib/constants.dart +++ /dev/null @@ -1,41 +0,0 @@ -abstract class Constants { - static const String ditoApiKey = String.fromEnvironment( - 'API_KEY', - defaultValue: '', - ); - - static const String ditoSecretKey = String.fromEnvironment( - 'SECRET_KEY', - defaultValue: '', - ); - - static const String firebaseAndroidApKey = String.fromEnvironment( - 'ANDROID_FIREBASE_APP_KEY', - defaultValue: '', - ); - - static const String firebaseAndroidAppID = String.fromEnvironment( - 'FIREBASE_MESSAGE_SENDER_ID', - defaultValue: '', - ); - - static const String firebaseMessageID = String.fromEnvironment( - 'ANDROID_FIREBASE_APP_ID', - defaultValue: '', - ); - - static const String firebaseProjectID = String.fromEnvironment( - 'FIREBASE_PROJECT_ID', - defaultValue: '', - ); - - static const String firebaseIosAppKey = String.fromEnvironment( - 'IOS_FIREBASE_APP_KEY', - defaultValue: '', - ); - - static const String firebaseIosAppID = String.fromEnvironment( - 'IOS_FIREBASE_APP_ID', - defaultValue: '', - ); -} diff --git a/example/lib/main.dart b/example/lib/main.dart index efd8640..629b5dd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,14 +6,22 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'app.dart'; -import 'constants.dart'; + +const apiKey = String.fromEnvironment( + 'API_KEY', + defaultValue: '', +); + +const secretKey = String.fromEnvironment( + 'SECRET_KEY', + defaultValue: '', +); @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(); DitoSDK dito = DitoSDK(); - dito.initialize( - apiKey: Constants.ditoApiKey, secretKey: Constants.ditoSecretKey); + dito.initialize(apiKey: apiKey, secretKey: secretKey); dito.onBackgroundPushNotificationHandler(message: message); } @@ -22,15 +30,14 @@ void main() async { await Firebase.initializeApp(); DitoSDK dito = DitoSDK(); - dito.initialize( - apiKey: Constants.ditoApiKey, secretKey: Constants.ditoSecretKey); + dito.initialize(apiKey: apiKey, secretKey: secretKey); await dito.initializePushNotificationService(); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); dito.notification.onMessageClick = (data) { if (kDebugMode) { - print(data.toJson()); + print(data); } }; diff --git a/example/pubspec.lock b/example/pubspec.lock deleted file mode 100644 index be2b324..0000000 --- a/example/pubspec.lock +++ /dev/null @@ -1,529 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _flutterfire_internals: - dependency: transitive - description: - name: _flutterfire_internals - sha256: b46f62516902afb04befa4b30eb6a12ac1f58ca8cb25fb9d632407259555dd3d - url: "https://pub.dev" - source: hosted - version: "1.3.39" - args: - dependency: transitive - description: - name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" - url: "https://pub.dev" - source: hosted - version: "2.5.0" - 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" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" - collection: - dependency: transitive - description: - name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.dev" - source: hosted - version: "1.18.0" - crypto: - dependency: transitive - description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab - url: "https://pub.dev" - source: hosted - version: "3.0.3" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - dbus: - dependency: transitive - description: - name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" - url: "https://pub.dev" - source: hosted - version: "0.7.10" - device_info_plus: - dependency: transitive - description: - name: device_info_plus - sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91 - url: "https://pub.dev" - source: hosted - version: "10.1.0" - device_info_plus_platform_interface: - dependency: transitive - description: - name: device_info_plus_platform_interface - sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 - url: "https://pub.dev" - source: hosted - version: "7.0.0" - dito_sdk: - dependency: "direct main" - description: - path: "../package" - relative: true - source: path - version: "0.5.5" - event_bus: - dependency: transitive - description: - name: event_bus - sha256: "44baa799834f4c803921873e7446a2add0f3efa45e101a054b1f0ab9b95f8edc" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - ffi: - dependency: transitive - description: - name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - file: - dependency: transitive - description: - name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - firebase_core: - dependency: "direct main" - description: - name: firebase_core - sha256: "5159984ce9b70727473eb388394650677c02c925aaa6c9439905e1f30966a4d5" - url: "https://pub.dev" - source: hosted - version: "3.2.0" - firebase_core_platform_interface: - dependency: transitive - description: - name: firebase_core_platform_interface - sha256: "1003a5a03a61fc9a22ef49f37cbcb9e46c86313a7b2e7029b9390cf8c6fc32cb" - url: "https://pub.dev" - source: hosted - version: "5.1.0" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - sha256: "23509cb3cddfb3c910c143279ac3f07f06d3120f7d835e4a5d4b42558e978712" - url: "https://pub.dev" - source: hosted - version: "2.17.3" - firebase_messaging: - dependency: "direct main" - description: - name: firebase_messaging - sha256: "156c4292aa63a6a7d508c68ded984cb38730d2823c3265e573cb1e94983e2025" - url: "https://pub.dev" - source: hosted - version: "15.0.3" - firebase_messaging_platform_interface: - dependency: transitive - description: - name: firebase_messaging_platform_interface - sha256: "10408c5ca242b7fc632dd5eab4caf8fdf18ebe88db6052980fa71a18d88bd200" - url: "https://pub.dev" - source: hosted - version: "4.5.41" - firebase_messaging_web: - dependency: transitive - description: - name: firebase_messaging_web - sha256: c7a756e3750679407948de665735e69a368cb902940466e5d68a00ea7aba1aaa - url: "https://pub.dev" - source: hosted - version: "3.8.11" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - flutter_local_notifications: - dependency: transitive - description: - name: flutter_local_notifications - sha256: "40e6fbd2da7dcc7ed78432c5cdab1559674b4af035fddbfb2f9a8f9c2112fcef" - url: "https://pub.dev" - source: hosted - version: "17.1.2" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" - url: "https://pub.dev" - source: hosted - version: "4.0.0+1" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7" - url: "https://pub.dev" - source: hosted - version: "7.1.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" - http: - dependency: transitive - description: - name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" - url: "https://pub.dev" - source: hosted - version: "10.0.4" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" - url: "https://pub.dev" - source: hosted - version: "3.0.3" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 - url: "https://pub.dev" - source: hosted - version: "3.0.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" - url: "https://pub.dev" - source: hosted - version: "0.8.0" - meta: - dependency: transitive - description: - name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" - url: "https://pub.dev" - source: hosted - version: "1.12.0" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - package_info_plus: - dependency: "direct main" - description: - name: package_info_plus - sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" - url: "https://pub.dev" - source: hosted - version: "4.2.0" - package_info_plus_platform_interface: - dependency: transitive - description: - name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" - url: "https://pub.dev" - source: hosted - version: "2.0.1" - path: - dependency: transitive - description: - name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.dev" - source: hosted - version: "1.9.0" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 - url: "https://pub.dev" - source: hosted - version: "6.0.2" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - provider: - dependency: "direct main" - description: - name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c - url: "https://pub.dev" - source: hosted - version: "6.1.2" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - sqflite: - dependency: transitive - description: - name: sqflite - sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d - url: "https://pub.dev" - source: hosted - version: "2.3.3+1" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" - url: "https://pub.dev" - source: hosted - version: "2.5.4" - sqflite_common_ffi: - dependency: transitive - description: - name: sqflite_common_ffi - sha256: "4d6137c29e930d6e4a8ff373989dd9de7bac12e3bc87bce950f6e844e8ad3bb5" - url: "https://pub.dev" - source: hosted - version: "2.3.3" - sqlite3: - dependency: transitive - description: - name: sqlite3 - sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295 - url: "https://pub.dev" - source: hosted - version: "2.4.3" - 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" - synchronized: - dependency: transitive - description: - name: synchronized - sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" - 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: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" - url: "https://pub.dev" - source: hosted - version: "0.7.0" - timezone: - dependency: transitive - description: - name: timezone - sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5 - url: "https://pub.dev" - source: hosted - version: "0.9.3" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" - url: "https://pub.dev" - source: hosted - version: "14.2.1" - web: - dependency: transitive - description: - name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" - url: "https://pub.dev" - source: hosted - version: "0.5.1" - win32: - dependency: transitive - description: - name: win32 - sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 - url: "https://pub.dev" - source: hosted - version: "5.5.1" - win32_registry: - dependency: transitive - description: - name: win32_registry - sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb" - url: "https://pub.dev" - source: hosted - version: "1.1.3" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d - url: "https://pub.dev" - source: hosted - version: "1.0.4" - xml: - dependency: transitive - description: - name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" - source: hosted - version: "6.5.0" -sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 3509fad..7b6df2e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -39,7 +39,7 @@ dependencies: package_info_plus: ^4.2.0 firebase_core: ^3.1.1 firebase_messaging: ^15.0.2 - dito_sdk: ^0.5.4 + dito_sdk: ^2.0.0 dev_dependencies: flutter_test: @@ -57,9 +57,6 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - assets: - - .env - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. diff --git a/package/Makefile b/package/Makefile new file mode 100644 index 0000000..97e6d67 --- /dev/null +++ b/package/Makefile @@ -0,0 +1,9 @@ +login: + buf registry login + +pull: + buf export buf.build/dito/sdk-service -o proto + +build: + buf build + buf generate proto diff --git a/package/buf.gen.yaml b/package/buf.gen.yaml new file mode 100644 index 0000000..472e2c4 --- /dev/null +++ b/package/buf.gen.yaml @@ -0,0 +1,9 @@ +version: v2 +managed: + enabled: true +plugins: + - remote: buf.build/protocolbuffers/dart:v21.1.2 + out: lib/proto +inputs: + - module: buf.build/dito/sdk-service + - module: buf.build/googleapis/googleapis diff --git a/package/lib/api/dito_api_interface.dart b/package/lib/api/dito_api_interface.dart new file mode 100644 index 0000000..ae0498c --- /dev/null +++ b/package/lib/api/dito_api_interface.dart @@ -0,0 +1,388 @@ +import 'package:http/http.dart' as http; +import 'package:uuid/uuid.dart'; +import 'dart:io'; + +import '../event/event_entity.dart'; +import '../event/navigation_entity.dart'; +import '../notification/notification_entity.dart'; +import '../proto/api.pb.dart' as rpcAPI; +import '../proto/google/protobuf/timestamp.pb.dart'; +import '../user/user_interface.dart'; +import '../utils/sha1.dart'; + +const url = 'https://sdk.dito.com.br/connect.sdk_api.v1.SDKService/Activity'; + +class AppInfoEntity { + String? build; + String? version; + String? id; + String? platform; + String? sdkVersion; + String? sdkBuild; + String? sdkLang; +} + +final class AppInfo extends AppInfoEntity { + AppInfo._internal(); + + static final appInfo = AppInfo._internal(); + + factory AppInfo() => appInfo; +} + +class ApiActivities { + final UserInterface _userInterface = UserInterface(); + final AppInfo _appInfo = AppInfo(); + + rpcAPI.DeviceOs _getPlatform() { + if (Platform.isIOS) { + return rpcAPI.DeviceOs.DEVICE_OS_IOS; + } else { + return rpcAPI.DeviceOs.DEVICE_OS_ANDROID; + } + } + + rpcAPI.DeviceInfo get deviceToken => rpcAPI.DeviceInfo() + ..os = _getPlatform() + ..token = _userInterface.data.token!; + + rpcAPI.SDKInfo get sdkInfo => rpcAPI.SDKInfo() + ..version = _appInfo.sdkVersion! + ..build = _appInfo.build! + ..lang = _appInfo.sdkLang!; + + rpcAPI.AppInfo get appInfo => rpcAPI.AppInfo() + ..id = _appInfo.id! + ..build = _appInfo.build! + ..platform = _appInfo.platform! + ..version = _appInfo.version!; + + rpcAPI.UserInfo get userInfo { + final user = rpcAPI.UserInfo(); + + if (_userInterface.data.email != null && + _userInterface.data.email!.isNotEmpty) { + user.email = _userInterface.data.email!; + } + + if (_userInterface.data.id != null && + _userInterface.data.id!.isNotEmpty) { + user.ditoId = _userInterface.data.id!; + } + + if (_userInterface.data.name != null && + _userInterface.data.name!.isNotEmpty) { + user.name = _userInterface.data.name!; + } + + if (_userInterface.data.birthday != null && + _userInterface.data.birthday!.isNotEmpty) { + user.birthday = _userInterface.data.birthday!; + } + + if (_userInterface.data.phone != null && + _userInterface.data.phone!.isNotEmpty) { + user.phone = _userInterface.data.phone!; + } + + if (_userInterface.data.gender != null && + _userInterface.data.gender!.isNotEmpty) { + user.gender = _userInterface.data.gender!; + } + + if (_userInterface.data.cpf != null && _userInterface.data.cpf!.isNotEmpty) { + user.customData['cpf'] = rpcAPI.UserInfo_CustomData(format: 'string', value: _userInterface.data.cpf); + } + + if (_userInterface.data.customData != null && _userInterface.data.customData!.isNotEmpty) { + user.customData.addAll( + _userInterface.data.customData!.map((key, value) { + final customDataValue = rpcAPI.UserInfo_CustomData(format: 'string', value: value); + return MapEntry(key, customDataValue); + }) + ); + } + + final addressData = _userInterface.data.address; + if (addressData != null) { + final hasAddress = [ + addressData.city, + addressData.country, + addressData.postalCode, + addressData.state, + addressData.street + ].any((field) => field != null && field.isNotEmpty); + + if (hasAddress) { + user.address = rpcAPI.UserInfo_Address(); + + if (_userInterface.data.address?.city != null && + _userInterface.data.address!.city!.isNotEmpty) { + user.address.city = _userInterface.data.address!.city!; + } + + if (_userInterface.data.address?.country != null && + _userInterface.data.address!.country!.isNotEmpty) { + user.address.country = _userInterface.data.address!.country!; + } + + if (_userInterface.data.address?.postalCode != null && + _userInterface.data.address!.postalCode!.isNotEmpty) { + user.address.postalCode = _userInterface.data.address!.postalCode!; + } + + if (_userInterface.data.address?.state != null && + _userInterface.data.address!.state!.isNotEmpty) { + user.address.state = _userInterface.data.address!.state!; + } + + if (_userInterface.data.address?.street != null && + _userInterface.data.address!.street!.isNotEmpty) { + user.address.street = _userInterface.data.address!.street!; + } + } + } + return user; + } + + rpcAPI.Activity identify({String? uuid, String? time}) { + final generatedUuid = uuid ?? Uuid().v4(); + final generatedTime = time != null + ? Timestamp.fromDateTime(DateTime.parse(time)) + : Timestamp.fromDateTime(DateTime.now()); + + return rpcAPI.Activity() + ..timestamp = generatedTime + ..id = generatedUuid + ..type = rpcAPI.ActivityType.ACTIVITY_IDENTIFY + ..userData = rpcAPI.Activity_UserDataActivity(); + } + + rpcAPI.Activity login({String? uuid, String? time}) { + final generatedUuid = uuid ?? Uuid().v4(); + final generatedTime = time != null + ? Timestamp.fromDateTime(DateTime.parse(time)) + : Timestamp.fromDateTime(DateTime.now()); + + return rpcAPI.Activity() + ..timestamp = generatedTime + ..id = generatedUuid + ..type = rpcAPI.ActivityType.ACTIVITY_TRACK + ..userLogin = (rpcAPI.Activity_UserLoginActivity()..utmSource = 'source'); + } + + rpcAPI.Activity trackEvent(EventEntity event, {String? uuid, String? time}) { + final generatedUuid = uuid ?? Uuid().v4(); + final generatedTime = time != null + ? Timestamp.fromDateTime(DateTime.parse(time)) + : Timestamp.fromDateTime(DateTime.now()); + + return rpcAPI.Activity() + ..timestamp = generatedTime + ..id = generatedUuid + ..type = rpcAPI.ActivityType.ACTIVITY_TRACK + ..track = (rpcAPI.Activity_TrackActivity() + ..event = event.action + ..revenue = event.revenue ?? 0 + ..currency = event.currency ?? 'BRL' + ..utmSource = 'source' + ..data.addAll(event.customData + ?.map((key, value) => MapEntry(key, value.toString())) ?? + {})); + } + + rpcAPI.Activity trackNavigation(NavigationEntity navigation, {String? uuid, String? time}) { + final generatedUuid = uuid ?? Uuid().v4(); + final generatedTime = time != null + ? Timestamp.fromDateTime(DateTime.parse(time)) + : Timestamp.fromDateTime(DateTime.now()); + + return rpcAPI.Activity() + ..timestamp = generatedTime + ..id = generatedUuid + ..type = rpcAPI.ActivityType.ACTIVITY_TRACK + ..trackNavigation = (rpcAPI.Activity_TrackNavigationActivity() + ..pageIdentifier = navigation.pageName + ..data.addAll(navigation.customData + ?.map((key, value) => MapEntry(key, value.toString())) ?? + {})); + } + + rpcAPI.Activity notificationClick(NotificationEntity notification, {String? uuid, String? time}) { + final generatedUuid = uuid ?? Uuid().v4(); + final generatedTime = time != null + ? Timestamp.fromDateTime(DateTime.parse(time)) + : Timestamp.fromDateTime(DateTime.now()); + + return rpcAPI.Activity() + ..timestamp = generatedTime + ..id = generatedUuid + ..type = rpcAPI.ActivityType.ACTIVITY_TRACK + ..trackPushClick = (rpcAPI.Activity_TrackPushClickActivity() + ..notification = (rpcAPI.NotificationInfo() + ..notificationId = notification.notification + ..dispatchId = notification.notificationLogId ?? "" + ..contactId = notification.contactId ?? "" + ..name = notification.name ?? "" + ..channel = 'mobile') + ..utmSource = 'source'); + } + + rpcAPI.Activity notificationReceived(NotificationEntity notification, {String? uuid, String? time}) { + final generatedUuid = uuid ?? Uuid().v4(); + final generatedTime = time != null + ? Timestamp.fromDateTime(DateTime.parse(time)) + : Timestamp.fromDateTime(DateTime.now()); + + return rpcAPI.Activity() + ..timestamp = generatedTime + ..id = generatedUuid + ..type = rpcAPI.ActivityType.ACTIVITY_TRACK + ..trackPushReceipt = (rpcAPI.Activity_TrackPushReceiptActivity() + ..notification = (rpcAPI.NotificationInfo() + ..notificationId = notification.notification + ..dispatchId = notification.notificationLogId ?? "" + ..contactId = notification.contactId ?? "" + ..name = notification.name ?? "" + ..channel = 'mobile') + ..utmSource = 'source'); + } + + rpcAPI.Activity registryToken(String token, {String? uuid, String? time}) { + final generatedUuid = uuid ?? Uuid().v4(); + final generatedTime = time != null + ? Timestamp.fromDateTime(DateTime.parse(time)) + : Timestamp.fromDateTime(DateTime.now()); + + return rpcAPI.Activity() + ..timestamp = generatedTime + ..id = generatedUuid + ..type = rpcAPI.ActivityType.ACTIVITY_REGISTER + ..tokenRegister = (rpcAPI.Activity_TokenRegisterActivity() + ..token = token + ..provider = rpcAPI.PushProvider.PROVIDER_FCM); + } + + rpcAPI.Activity pingToken(String token, {String? uuid, String? time}) { + final generatedUuid = uuid ?? Uuid().v4(); + final generatedTime = time != null + ? Timestamp.fromDateTime(DateTime.parse(time)) + : Timestamp.fromDateTime(DateTime.now()); + + return rpcAPI.Activity() + ..timestamp = generatedTime + ..id = generatedUuid + ..type = rpcAPI.ActivityType.ACTIVITY_REGISTER + ..tokenPing = (rpcAPI.Activity_TokenPingActivity() + ..token = token + ..provider = rpcAPI.PushProvider.PROVIDER_FCM); + } + + rpcAPI.Activity removeToken(String token, {String? uuid, String? time}) { + final generatedUuid = uuid ?? Uuid().v4(); + final generatedTime = time != null + ? Timestamp.fromDateTime(DateTime.parse(time)) + : Timestamp.fromDateTime(DateTime.now()); + + return rpcAPI.Activity() + ..timestamp = generatedTime + ..id = generatedUuid + ..type = rpcAPI.ActivityType.ACTIVITY_REGISTER + ..tokenUnregister = (rpcAPI.Activity_TokenUnregisterActivity() + ..token = token + ..provider = rpcAPI.PushProvider.PROVIDER_FCM); + } +} + +class ApiInterface { + String? _apiKey; + String? _secretKey; + + static final ApiInterface _instance = ApiInterface._internal(); + + factory ApiInterface() { + return _instance; + } + + ApiInterface._internal(); + + void setKeys(String apiKey, String secretKey) { + _instance._apiKey = apiKey; + _instance._secretKey = convertToSHA1(secretKey); + } + + ApiRequest createRequest(List activities) { + ApiActivities apiActivities = ApiActivities(); + + final device = apiActivities.deviceToken; + final sdk = apiActivities.sdkInfo; + final app = apiActivities.appInfo; + final user = apiActivities.userInfo; + + final request = rpcAPI.Request() + ..user = user + ..device = device + ..sdk = sdk + ..app = app + ..activities.addAll(activities); + + return ApiRequest(request, _apiKey, _secretKey); + } +} + +class ApiRequest { + final rpcAPI.Request _request; + final String? _apiKey; + final String? _secretKey; + + ApiRequest(this._request, this._apiKey, this._secretKey); + + void _checkConfiguration() { + if (_apiKey == null || _secretKey == null) { + throw Exception( + 'API key and Secret Key must be initialized before using. Please call the initialize() method first.'); + } + } + + Future call() async { + _checkConfiguration(); + + final response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/proto', + 'platform_api_key': _apiKey!, + 'sha1_signature': _secretKey!, + }, + body: _request.writeToBuffer(), + ); + + if (response.statusCode == 200) { + final responseProto = rpcAPI.Response.fromBuffer(response.bodyBytes); + for (var responseData in responseProto.response) { + if (responseData.hasError()) { + final error = responseData.error; + + switch (error.code) { + case rpcAPI.ErrorCode.ERROR_INVALID_REQUEST: + throw ('Invalid request format.'); + case rpcAPI.ErrorCode.ERROR_UNAUTHORIZED: + throw ('Unauthorized access.'); + case rpcAPI.ErrorCode.ERROR_NOT_FOUND: + throw ('Resource not found.'); + case rpcAPI.ErrorCode.ERROR_INTERNAL: + throw ('Internal server error.'); + case rpcAPI.ErrorCode.ERROR_NOT_IMPLEMENTED: + throw ('Feature not implemented.'); + default: + throw ('Unknown error occurred.'); + } + } + } + + return true; + } + + throw ('Unknown error occurred.'); + } +} diff --git a/package/lib/data/database.dart b/package/lib/data/database.dart index 0db1e2f..49c327b 100644 --- a/package/lib/data/database.dart +++ b/package/lib/data/database.dart @@ -9,7 +9,7 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart'; class LocalDatabase { static const String _dbName = 'dito-offline.db'; static Database? _database; - final tables = {"notification": "notification", "events": "events"}; + final tables = {"notification": "notification", "events": "events", "user": "user"}; static final LocalDatabase _instance = LocalDatabase._internal(); @@ -67,17 +67,19 @@ class LocalDatabase { await db.execute(''' CREATE TABLE events ( id INTEGER PRIMARY KEY AUTOINCREMENT, - eventName TEXT, - eventMoment TEXT, - revenue REAL, - customData TEXT + name TEXT, + event TEXT, + uuid TEXT, + createdAt TEXT ); '''); await db.execute(''' - CREATE TABLE notification ( + CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, - event TEXT, - token TEXT + name TEXT, + user TEXT, + uuid TEXT, + createdAt TEXT ); '''); } catch (e) { diff --git a/package/lib/data/dito_api.dart b/package/lib/data/dito_api.dart deleted file mode 100644 index 784ce36..0000000 --- a/package/lib/data/dito_api.dart +++ /dev/null @@ -1,173 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:http/http.dart' as http; - -import '../event/event_entity.dart'; -import '../user/user_entity.dart'; -import '../utils/sha1.dart'; - -class DitoApi { - final String _platform = Platform.isIOS ? 'iPhone' : 'Android'; - String? _apiKey; - String? _secretKey; - late Map _assign; - - static final DitoApi _instance = DitoApi._internal(); - - factory DitoApi() { - return _instance; - } - - DitoApi._internal(); - - void setKeys(String apiKey, String secretKey) { - _instance._apiKey = apiKey; - _instance._secretKey = secretKey; - _instance._assign = { - 'platform_api_key': apiKey, - 'sha1_signature': convertToSHA1(secretKey), - }; - } - - void _checkConfiguration() { - if (_apiKey == null || _secretKey == null) { - throw Exception( - 'API key and Secret Key must be initialized before using. Please call the initialize() method first.'); - } - } - - Future _post(String url, String path, - {Map? queryParameters, Map? body}) { - _checkConfiguration(); - - final uri = Uri.https(url, path, queryParameters); - - return http.post( - uri, - body: body, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': _platform, - }, - ); - } - - Future _put(String url, String path, - {Map? queryParameters, Map? body}) { - _checkConfiguration(); - - final uri = Uri.https(url, path, queryParameters); - - return http.put( - uri, - body: body, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': _platform, - }, - ); - } - - Future identify(UserEntity user) async { - final queryParameters = { - 'user_data': jsonEncode(user.toJson()), - }; - - queryParameters.addAll(_assign); - - const url = 'login.plataformasocial.com.br'; - final path = 'users/portal/${user.id}/signup'; - - return await _post(url, path, queryParameters: queryParameters); - } - - Future updateUserData(UserEntity user) async { - final queryParameters = { - 'user_data': jsonEncode(user.toJson()), - }; - - queryParameters.addAll(_assign); - - const url = 'login.plataformasocial.com.br'; - final path = 'users/${user.id}'; - - return await _put(url, path, - queryParameters: queryParameters, - body: {'user_data': jsonEncode(user.toJson())}); - } - - Future trackEvent(EventEntity event, UserEntity user) async { - final body = { - 'id_type': 'id', - 'network_name': 'pt', - 'event': jsonEncode(event.toJson()) - }; - - const url = 'events.plataformasocial.com.br'; - final path = 'users/${user.id}'; - - body.addAll(_assign); - - return await _post(url, path, body: body); - } - - Future openNotification( - String notificationId, String identifier, String reference) async { - final queryParameters = { - 'channel_type': 'mobile', - }; - - final body = { - 'identifier': identifier, - 'reference': reference, - }; - - queryParameters.addAll(_assign); - - const url = 'notification.plataformasocial.com.br'; - final path = 'notifications/$notificationId/open'; - - return await _post(url, path, queryParameters: queryParameters, body: body); - } - - Future registryToken(String token, UserEntity user) async { - if (user.isNotValid) { - throw Exception( - 'User registration is required. Please call the identify() method first.'); - } - - final queryParameters = { - 'id_type': 'id', - 'token': token, - 'platform': _platform, - }; - - queryParameters.addAll(_assign); - - const url = 'notification.plataformasocial.com.br'; - final path = 'users/${user.id}/mobile-tokens/'; - - return await _post(url, path, queryParameters: queryParameters); - } - - Future removeToken(String token, UserEntity user) async { - if (user.isNotValid) { - throw Exception( - 'User registration is required. Please call the identify() method first.'); - } - - final queryParameters = { - 'id_type': 'id', - 'token': token, - 'platform': _platform, - }; - - queryParameters.addAll(_assign); - - const url = 'notification.plataformasocial.com.br'; - final path = 'users/${user.id}/mobile-tokens/disable'; - - return await _post(url, path, queryParameters: queryParameters); - } -} diff --git a/package/lib/dito_sdk.dart b/package/lib/dito_sdk.dart index 60972b8..06b4dfc 100644 --- a/package/lib/dito_sdk.dart +++ b/package/lib/dito_sdk.dart @@ -1,8 +1,11 @@ library dito_sdk; +import 'dart:io'; + import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:package_info_plus/package_info_plus.dart'; -import 'data/dito_api.dart'; +import 'api/dito_api_interface.dart'; import 'event/event_interface.dart'; import 'notification/notification_interface.dart'; import 'user/user_interface.dart'; @@ -10,10 +13,11 @@ import 'user/user_interface.dart'; /// DitoSDK is a singleton class that provides various methods to interact with Dito API /// and manage user data, events, and push notifications. class DitoSDK { - final DitoApi _api = DitoApi(); + final ApiInterface _api = ApiInterface(); final UserInterface _userInterface = UserInterface(); final EventInterface _eventInterface = EventInterface(); final NotificationInterface _notificationInterface = NotificationInterface(); + final AppInfo _appInfo = AppInfo(); static final DitoSDK _instance = DitoSDK._internal(); @@ -41,7 +45,15 @@ class DitoSDK { /// [apiKey] - The API key for the Dito platform. /// [secretKey] - The secret key for the Dito platform. void initialize({required String apiKey, required String secretKey}) async { + final packageInfo = await PackageInfo.fromPlatform(); _api.setKeys(apiKey, secretKey); + _appInfo.platform = Platform.isAndroid ? 'Android' : 'Apple iPhone'; + _appInfo.sdkLang = "Flutter"; + _appInfo.sdkVersion = '2.0.0'; + _appInfo.sdkBuild = '1'; + _appInfo.build = packageInfo.buildNumber; + _appInfo.version = packageInfo.version; + _appInfo.id = packageInfo.packageName; } /// This method initializes the push notification service using Firebase. diff --git a/package/lib/data/event_database.dart b/package/lib/event/event_dao.dart similarity index 50% rename from package/lib/data/event_database.dart rename to package/lib/event/event_dao.dart index f80dd83..b94de2a 100644 --- a/package/lib/data/event_database.dart +++ b/package/lib/event/event_dao.dart @@ -1,31 +1,73 @@ +import 'dart:convert'; + +import 'package:dito_sdk/notification/notification_entity.dart'; import 'package:flutter/foundation.dart'; -import '../event/event_entity.dart'; -import 'database.dart'; +import '../data/database.dart'; +import 'event_entity.dart'; +import 'navigation_entity.dart'; + +enum EventsNames { click, received, navigate, track } /// EventDatabaseService is a singleton class that provides methods to interact with a SQLite database /// for storing and managing events. -class EventDatabase { +class EventDAO { static final LocalDatabase _database = LocalDatabase(); - static final EventDatabase _instance = EventDatabase._internal(); + static final EventDAO _instance = EventDAO._internal(); + + get _table => _database.tables["events"]; /// Factory constructor to return the singleton instance of EventDatabaseService. - factory EventDatabase() { + factory EventDAO() { return _instance; } /// Private named constructor for internal initialization of singleton instance. - EventDatabase._internal(); + EventDAO._internal(); /// Method to insert a new event into the events table. /// /// [event] - The EventEntity object to be inserted. + /// [navigation] - The NavigationEntity object to be inserted. + /// [notification] - The NotificationEntity object to be inserted. + /// [uuid] - Event Identifier. /// Returns a Future that completes with the row id of the inserted event. - Future create(EventEntity event) async { + Future create(EventsNames eventType, String uuid, + {EventEntity? event, + NavigationEntity? navigation, + NotificationEntity? notification}) async { try { - return await _database.insert( - _database.tables["events"]!, event.toMap()) > - 0; + if (event != null) { + return await _database.insert(_table, { + "name": eventType.name, + "event": jsonEncode(event.toJson()), + "uuid": uuid, + "createdAt": DateTime.now().toIso8601String() + }) > + 0; + } + + if (navigation != null) { + return await _database.insert(_table, { + "name": eventType.name, + "event": jsonEncode(navigation.toJson()), + "uuid": uuid, + "createdAt": DateTime.now().toIso8601String() + }) > + 0; + } + + if (notification != null) { + return await _database.insert(_table, { + "name": eventType.name, + "event": jsonEncode(notification.toJson()), + "uuid": uuid, + "createdAt": DateTime.now().toIso8601String() + }) > + 0; + } + + return false; } catch (e) { if (kDebugMode) { print('Error inserting event: $e'); @@ -41,8 +83,8 @@ class EventDatabase { Future delete(EventEntity event) async { try { return await _database.delete( - _database.tables["events"]!, - 'eventName = ? AND eventMoment = ?', + _table, + 'name = ? AND createdAt = ?', [event], ) > 0; @@ -56,10 +98,9 @@ class EventDatabase { /// Method to retrieve all events from the events table. /// Returns a Future that completes with a list of Map objects. - Future> fetchAll() async { + Future fetchAll() async { try { - final maps = await _database.fetchAll(_database.tables["events"]!); - return maps.map((map) => EventEntity.fromMap(map)); + return await _database.fetchAll(_table); } catch (e) { if (kDebugMode) { print('Error retrieving events: $e'); @@ -72,7 +113,7 @@ class EventDatabase { /// Returns a Future that completes with the number of rows deleted. Future clearDatabase() async { try { - return _database.clearDatabase(_database.tables["events"]!); + return _database.clearDatabase(_table); } catch (e) { if (kDebugMode) { print('Error clearing database: $e'); diff --git a/package/lib/event/event_entity.dart b/package/lib/event/event_entity.dart index b646b53..b9a0fcb 100644 --- a/package/lib/event/event_entity.dart +++ b/package/lib/event/event_entity.dart @@ -1,43 +1,38 @@ import 'dart:convert'; class EventEntity { - String eventName; - String? eventMoment; + String action; + String? createdAt; double? revenue; + String? currency; Map? customData; EventEntity({ - required this.eventName, + required this.action, this.revenue, - this.eventMoment, + this.createdAt, + this.currency, this.customData, }); factory EventEntity.fromMap(Map map) { return EventEntity( - eventName: map['eventName'], + action: map['action'], revenue: map['revenue'], - eventMoment: map['eventMoment'], + createdAt: map['createdAt'], + currency: map['currency'], customData: - map['customData'] != null ? json.decode(map['customData']) : null, + map['customData'] != null ? jsonDecode(map['customData']) : null, ); } - Map toMap() { - return { - 'eventName': eventName, - 'eventMoment': eventMoment, - 'revenue': revenue, - 'customData': customData != null ? jsonEncode(customData) : null, - }; - } - Map toJson() { final json = { - 'action': eventName, + 'action': action, 'revenue': revenue, + 'currency': currency, 'data': customData, - 'created_at': eventMoment + 'created_at': createdAt }; json.removeWhere((key, value) => value == null); diff --git a/package/lib/event/event_interface.dart b/package/lib/event/event_interface.dart index dcc307a..1609dc7 100644 --- a/package/lib/event/event_interface.dart +++ b/package/lib/event/event_interface.dart @@ -1,38 +1,84 @@ import 'package:flutter/foundation.dart'; -import '../utils/custom_data.dart'; import 'event_entity.dart'; import 'event_repository.dart'; +import 'navigation_entity.dart'; -/// EventInterface is an interface for managing events and communicating with the event repository +/// `EventInterface` provides an interface for tracking user events and navigation actions. +/// It interacts with the `EventRepository` to save these events in the backend. interface class EventInterface { + /// Repository that handles the communication for event tracking. final EventRepository _repository = EventRepository(); - /// Tracks an event by saving and sending it to the event repository. + /// Tracks a user event. /// - /// [event] - The EventEntity object containing event data. - /// Returns a Future that completes with true if the event was successfully tracked. - Future trackEvent(EventEntity event) async { + /// [action] - The action name (e.g., a button click or form submission). + /// [createdAt] - The event creation time, defaults to the current UTC time if not provided. + /// [revenue] - The revenue amount associated with the event, optional. + /// [currency] - The currency for the revenue, optional. + /// [customData] - A map of additional custom data related to the event, optional. + /// + /// Returns a `Future` that completes with `true` if the event was tracked successfully, + /// or `false` if there was an error. + Future track( + {required String action, + String? createdAt, + double? revenue, + String? currency, + Map? customData}) async { try { + // Get the current local time and convert it to UTC for accurate event logging. DateTime localDateTime = DateTime.now(); DateTime utcDateTime = localDateTime.toUtc(); - String eventMoment = utcDateTime.toIso8601String(); - event.eventMoment ??= eventMoment; + // Create an event entity using the provided data. + final event = EventEntity( + action: action, + createdAt: createdAt ?? utcDateTime.toIso8601String(), // Default to current UTC time if not provided. + revenue: revenue, + currency: currency, + customData: customData); - final version = await customDataVersion; - if (event.customData == null) { - event.customData = version; - } else { - event.customData?.addAll(version); + // Track the event using the repository and return the result. + return await _repository.track(event); + } catch (e) { + if (kDebugMode) { + print('Error tracking event: $e'); // Log the error in debug mode. } + return false; // Return false if there was an error. + } + } + + /// Tracks a navigation event when the user navigates to a new page or screen. + /// + /// [name] - The name of the page the user navigated to. + /// [createdAt] - The navigation event creation time, defaults to the current UTC time if not provided. + /// [customData] - A map of additional custom data related to the navigation event, optional. + /// + /// Returns a `Future` that completes with `true` if the navigation event was tracked successfully, + /// or `false` if there was an error. + Future navigate( + {required String name, + String? createdAt, + Map? customData}) async { + try { + // Get the current local time and convert it to UTC for accurate navigation logging. + DateTime localDateTime = DateTime.now(); + DateTime utcDateTime = localDateTime.toUtc(); + + // Create a navigation entity with the provided data. + final navigation = NavigationEntity( + pageName: name, + createdAt: createdAt ?? utcDateTime.toIso8601String(), // Default to current UTC time if not provided. + customData: customData); - return await _repository.trackEvent(event); + // Track the navigation event using the repository and return the result. + return await _repository.navigate(navigation); } catch (e) { if (kDebugMode) { - print('Error tracking event: $e'); + print('Error tracking navigation event: $e'); // Log the error in debug mode. } - return false; + return false; // Return false if there was an error. } } } diff --git a/package/lib/event/event_repository.dart b/package/lib/event/event_repository.dart index b918fa9..04c487c 100644 --- a/package/lib/event/event_repository.dart +++ b/package/lib/event/event_repository.dart @@ -1,18 +1,21 @@ import 'dart:async'; +import 'dart:convert'; -import 'package:dito_sdk/user/user_repository.dart'; +import 'package:dito_sdk/notification/notification_entity.dart'; import 'package:flutter/foundation.dart'; - -import '../data/dito_api.dart'; -import '../data/event_database.dart'; +import '../api/dito_api_interface.dart'; +import '../proto/api.pb.dart'; +import '../user/user_repository.dart'; +import 'event_dao.dart'; import 'event_entity.dart'; +import 'navigation_entity.dart'; /// EventRepository is responsible for managing events by interacting with /// the local database and the Dito API. class EventRepository { - final DitoApi _api = DitoApi(); + final ApiInterface _api = ApiInterface(); final UserRepository _userRepository = UserRepository(); - final _database = EventDatabase(); + final _database = EventDAO(); /// Tracks an event by saving it to the local database if the user is not registered, /// or by sending it to the Dito API if the user is registered. @@ -20,17 +23,35 @@ class EventRepository { /// [event] - The EventEntity object containing event data. /// Returns a Future that completes with true if the event was successfully tracked, /// or false if an error occurred. - Future trackEvent(EventEntity event) async { + Future track(EventEntity event) async { + final activity = ApiActivities().trackEvent(event); + // If the user is not registered, save the event to the local database if (_userRepository.data.isNotValid) { - return await _database.create(event); + return await _database.create(EventsNames.track, activity.id, event: event); } // Otherwise, send the event to the Dito API - return await _api - .trackEvent(event, _userRepository.data) - .then((response) => true) - .catchError((e) => false); + return await _api.createRequest([activity]).call(); + } + + /// Tracks an event by saving it to the local database if the user is not registered, + /// or by sending it to the Dito API if the user is registered. + /// + /// [event] - The EventEntity object containing event data. + /// Returns a Future that completes with true if the event was successfully tracked, + /// or false if an error occurred. + Future navigate(NavigationEntity navigation) async { + final activity = ApiActivities().trackNavigation(navigation); + + // If the user is not registered, save the event to the local database + if (_userRepository.data.isNotValid) { + return await _database.create(EventsNames.navigate, activity.id, + navigation: navigation); + } + + // Otherwise, send the event to the Dito API + return await _api.createRequest([activity]).call(); } /// Verifies and processes any pending events. @@ -38,16 +59,48 @@ class EventRepository { /// Throws an exception if the user is not valid. Future verifyPendingEvents() async { try { - final events = await _database.fetchAll(); + final rows = await _database.fetchAll(); + List activities = []; + + for (final row in rows) { + final eventName = row["name"]; + final uuid = row["uuid"] as String? ?? null; + final time = row["createdAt"] as String? ?? null; + + switch (eventName) { + case 'track': + final event = + EventEntity.fromMap(jsonDecode(row["event"] as String)); + activities.add(ApiActivities().trackEvent(event, uuid: uuid, time: time)); + break; + case 'received': + final event = + NotificationEntity.fromMap(jsonDecode(row["event"] as String)); + activities.add(ApiActivities().notificationReceived(event, uuid: uuid, time: time)); + break; + case 'click': + final event = + NotificationEntity.fromMap(jsonDecode(row["event"] as String)); + activities.add(ApiActivities().notificationClick(event, uuid: uuid, time: time)); + break; + case 'navigate': + final event = + NavigationEntity.fromMap(jsonDecode(row["event"] as String)); + activities.add(ApiActivities().trackNavigation(event, uuid: uuid, time: time)); + break; + default: + break; + } + } - for (final event in events) { - await trackEvent(event); + if (activities.isNotEmpty) { + await _api.createRequest(activities).call(); } - await _database.clearDatabase(); + return await _database.clearDatabase(); } catch (e) { if (kDebugMode) { - print('Error verifying pending events: $e'); + print('Error verifying pending events on notification: $e'); } rethrow; } diff --git a/package/lib/event/navigation_entity.dart b/package/lib/event/navigation_entity.dart new file mode 100644 index 0000000..d46038b --- /dev/null +++ b/package/lib/event/navigation_entity.dart @@ -0,0 +1,31 @@ +import 'dart:convert'; + +class NavigationEntity { + String pageName; + String? createdAt; + Map? customData; + + + NavigationEntity({ + required this.pageName, + this.createdAt, + this.customData, + }); + + factory NavigationEntity.fromMap(Map map) { + return NavigationEntity( + pageName: map['pageName'], + createdAt: map['createdAt'], + customData: + map['customData'] != null ? jsonDecode(map['customData']) : null, + ); + } + + Map toJson() { + return { + 'pageName': pageName, + 'createdAt': createdAt, + 'data': customData, + }; + } +} diff --git a/package/lib/notification/notification_controller.dart b/package/lib/notification/notification_controller.dart index 6c55209..9fdefb1 100644 --- a/package/lib/notification/notification_controller.dart +++ b/package/lib/notification/notification_controller.dart @@ -1,15 +1,15 @@ import 'dart:async'; import 'dart:convert'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'notification_entity.dart'; -import 'notification_repository.dart'; class NotificationController { late FlutterLocalNotificationsPlugin localNotificationsPlugin; - final NotificationRepository _repository = NotificationRepository(); - Function(DataPayload)? _selectNotification; + Function(RemoteMessage)? _selectNotification; /// Android-specific notification details. AndroidNotificationDetails androidNotificationDetails = @@ -44,7 +44,8 @@ class NotificationController { /// Initializes the local notifications plugin. /// /// [onSelectNotification] - Callback function to handle notification selection. - Future initialize(Function(DataPayload) selectNotification) async { + Future initialize( + Function(RemoteMessage) selectNotification) async { _selectNotification = selectNotification; localNotificationsPlugin = FlutterLocalNotificationsPlugin(); @@ -78,14 +79,25 @@ class NotificationController { /// /// [notification] - NotificationEntity object containing notification details. void showNotification(NotificationEntity notification) async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + final appName = packageInfo.appName; + + final displayInfo = NotificationDisplayEntity( + id: notification.hashCode, + notificationId: notification.notification, + title: notification.details?.title ?? appName, + body: notification.details?.message ?? "", + image: notification.details?.image, + data: notification.details?.toJson()); + localNotificationsPlugin.show( - notification.id, // Notification ID. - notification.title, // Notification title. - notification.body, // Notification body. + displayInfo.id, // Notification ID. + displayInfo.title, // Notification title. + displayInfo.body, // Notification body. NotificationDetails( android: androidNotificationDetails, iOS: darwinNotificationDetails), payload: - jsonEncode(notification.payload?.toJson()), // Notification payload. + jsonEncode(notification.details?.toJson()), // Notification payload. ); } @@ -96,11 +108,7 @@ class NotificationController { final payload = response?.payload; if (payload != null && payload.isNotEmpty) { - final data = DataPayload.fromPayload(jsonDecode(payload)); - - await _repository.notifyOpenDeepLink(data.notification); - - if (_selectNotification != null) _selectNotification!(data); + if (_selectNotification != null) _selectNotification!(jsonDecode(payload) as RemoteMessage); } } } diff --git a/package/lib/notification/notification_entity.dart b/package/lib/notification/notification_entity.dart index 94dea79..04ebb6d 100644 --- a/package/lib/notification/notification_entity.dart +++ b/package/lib/notification/notification_entity.dart @@ -1,33 +1,45 @@ import 'dart:io'; -class Details { +class DetailsEntity { final String? link; final String? title; final String message; String? image; - Details(this.title, this.message, this.link, this.image); + DetailsEntity(this.title, this.message, this.link, this.image); - factory Details.fromJson(dynamic json) { + factory DetailsEntity.fromJson(dynamic json) { assert(json is Map); - return Details(json["title"], json["message"], json["link"], json["image"]); + return DetailsEntity( + json["title"], json["message"], json["link"], json["image"]); } Map toJson() => {'link': link, 'message': message, 'title': title, 'image': image}; } -class DataPayload { +class NotificationEntity { + final String notification; + final String? notificationLogId; + final String? contactId; final String? reference; - final String? identifier; - final String? notification; - final String? notification_log_id; - final Details details; + final String? userId; + final String? name; + final DetailsEntity? details; + final String? createdAt; - DataPayload(this.reference, this.identifier, this.notification, - this.notification_log_id, this.details); + NotificationEntity({ + required this.notification, + this.notificationLogId, + this.contactId, + this.reference, + this.userId, + this.name, + this.details, + this.createdAt, + }); - factory DataPayload.fromMap(dynamic json) { + factory NotificationEntity.fromMap(dynamic json) { final String? image; assert(json is Map); @@ -43,53 +55,63 @@ class DataPayload { json["notification"]?["body"] ?? json["data"]["message"]; final String link = json["data"]["link"]; - final Details details = Details(title, message, link, image); + final DetailsEntity details = DetailsEntity(title, message, link, image); - return DataPayload( - json["data"]["reference"], - json["data"]["user_id"], - json["data"]["notification"], - json["data"]["notification_log_id"], - details, - ); + DateTime localDateTime = DateTime.now(); + DateTime utcDateTime = localDateTime.toUtc(); + + return NotificationEntity( + notification: json["data"]["notification"], + notificationLogId: json["data"]["log_id"], + contactId: json["messageId"], + reference: json["data"]["reference"], + userId: json["data"]["user_id"], + name: json["data"]["notification_name"], + createdAt: utcDateTime.toIso8601String(), + details: details); } - factory DataPayload.fromPayload(dynamic json) { + factory NotificationEntity.fromPayload(dynamic json) { assert(json is Map); - return DataPayload( - json["reference"], - json["identifier"], - json["notification_log_id"], - json["notification"], - Details.fromJson(json["details"]), + return NotificationEntity( + notification: json["notification"], + notificationLogId: json["notificationLogId"], + contactId: json["contactId"], + reference: json["reference"], + userId: json["userId"], + name: json["name"], + createdAt: json["createdAt"], + details: DetailsEntity.fromJson(json["details"]), ); } Map toJson() => { - 'reference': reference, - 'identifier': identifier, 'notification': notification, - 'notification_log_id': notification_log_id, - 'details': details.toJson() + 'notificationLogId': notificationLogId, + 'contactId': contactId, + 'reference': reference, + 'userId': userId, + 'name': name, + 'createdAt': createdAt, + 'details': details, }; } -class NotificationEntity { +class NotificationDisplayEntity { int id; String title; String body; String? notificationId; String? image; + Map? data; - DataPayload? payload; - - NotificationEntity({ + NotificationDisplayEntity({ required this.id, required this.title, required this.body, this.notificationId, this.image, - this.payload, + this.data, }); } diff --git a/package/lib/notification/notification_events.dart b/package/lib/notification/notification_events.dart index d985b0b..d55fd9f 100644 --- a/package/lib/notification/notification_events.dart +++ b/package/lib/notification/notification_events.dart @@ -1,10 +1,10 @@ -import 'package:dito_sdk/notification/notification_entity.dart'; import 'package:event_bus/event_bus.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; class MessageClickedEvent { - DataPayload data; + RemoteMessage message; - MessageClickedEvent(this.data); + MessageClickedEvent(this.message); } class NotificationEvents { diff --git a/package/lib/notification/notification_interface.dart b/package/lib/notification/notification_interface.dart index 6b4ffe0..21ca373 100644 --- a/package/lib/notification/notification_interface.dart +++ b/package/lib/notification/notification_interface.dart @@ -4,62 +4,84 @@ import 'dart:io'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import '../data/dito_api.dart'; import '../user/user_interface.dart'; import 'notification_controller.dart'; import 'notification_entity.dart'; import 'notification_events.dart'; import 'notification_repository.dart'; -/// NotificationInterface is an interface for communication with the notification repository and notification controller +/// `NotificationInterface` manages notifications, handling initialization, token management, +/// and listening for notification events. It integrates with Firebase Messaging and custom notification flows. class NotificationInterface { - late void Function(DataPayload payload) onMessageClick; + /// Callback to be invoked when a notification is clicked. + late void Function(RemoteMessage message) onMessageClick; + + /// Notification repository to manage notification-related data. final NotificationRepository _repository = NotificationRepository(); + + /// Controller to manage notification display and actions. final NotificationController _controller = NotificationController(); + + /// Manages notification events, such as when a notification is clicked. final NotificationEvents _notificationEvents = NotificationEvents(); - final DitoApi _api = DitoApi(); + + /// Interface for accessing user-related data, like user tokens. final UserInterface _userInterface = UserInterface(); + + /// A flag to ensure initialization is only performed once. bool initialized = false; - /// This method initializes notification controller and notification repository. - /// Start listening to notifications + /// Retrieves the current Firebase Messaging token. + Future get token async => await FirebaseMessaging.instance.getToken(); + + /// Initializes the notification interface, including Firebase Messaging, + /// setting up token management, and listening for notification events. Future initialize() async { + // Ensure Firebase is initialized before proceeding. if (Firebase.apps.isEmpty) { throw 'Firebase not initialized'; } + // Return if already initialized to avoid redundant setup. if (initialized) return; + // Enable automatic initialization of Firebase Messaging. await FirebaseMessaging.instance.setAutoInitEnabled(true); + // For iOS, set notification presentation options. if (Platform.isIOS) { await FirebaseMessaging.instance .setForegroundNotificationPresentationOptions( badge: true, sound: true, alert: true); } + // Listen for incoming notifications when the app is in the foreground. FirebaseMessaging.onMessage.listen(onMessage); + // Handle the Firebase Messaging token for the current user. _handleToken(); + // Initialize the notification controller and handle notification selection. await _controller.initialize(onSelectNotification); + + // Start listening for notification streams. _listenStream(); + + // Mark as initialized to prevent reinitialization. initialized = true; } + /// Handles retrieving and updating the Firebase Messaging token for the user. + /// Listens for token refreshes and updates the user data and token repository. void _handleToken() async { - _userInterface.data.token = await getFirebaseToken(); + // Assign the current token to the user's data. + _userInterface.data.token = await token; + + // Listen for token refresh events and update the user data and token repository. FirebaseMessaging.instance.onTokenRefresh.listen((token) { - final lastToken = _userInterface.data.token; - if (lastToken != token) { - if (lastToken != null && lastToken.isNotEmpty) { - removeToken(lastToken); - } - registryToken(token); - _userInterface.data.token = token; - } + _userInterface.data.token = token; + _userInterface.token.pingToken(token); // Send token to backend. }).onError((err) { if (kDebugMode) { print('Error getting token: $err'); @@ -67,114 +89,149 @@ class NotificationInterface { }); } - /// Gets the current FCM token for the device. - /// - /// Returns the token as a String or null if not available. - Future getFirebaseToken() => FirebaseMessaging.instance.getToken(); - - // This method turns off the streams when this class is unmounted + /// Disposes of notification streams, ensuring that all resources are released. void dispose() { _repository.didReceiveLocalNotificationStream.close(); _repository.selectNotificationStream.close(); } - // This method initializes the listeners on streams + /// Listens for events in the notification streams and triggers appropriate actions. _listenStream() { + // Listen for locally received notifications and handle their display and storage. _repository.didReceiveLocalNotificationStream.stream .listen((NotificationEntity receivedNotification) async { + // Show the notification through the controller. _controller.showNotification(receivedNotification); - await notifyReceivedNotification(receivedNotification.notificationId!); - }); - _repository.selectNotificationStream.stream - .listen((DataPayload data) async { - _notificationEvents.stream.fire(MessageClickedEvent(data)); - // Only sends the event if the message is linked to a notification - if (data.notification != null && - data.identifier != null && - data.reference != null) { - await _api.openNotification( - data.notification!, data.identifier!, data.reference!); + // Mark the notification as received in the repository. + await _repository.received(NotificationEntity( + notification: receivedNotification.notification, + notificationLogId: receivedNotification.notificationLogId, + contactId: receivedNotification.contactId, + name: receivedNotification.name)); + }); - onMessageClick(data); - } + // Listen for selected notifications (e.g., when a user taps on a notification). + _repository.selectNotificationStream.stream + .listen((RemoteMessage message) async { + // Trigger a click event in the notification events system. + _notificationEvents.stream.fire(MessageClickedEvent(message)); + + final data = message.data; + final notification = NotificationEntity( + notification: data["notification"], + notificationLogId: data["notificationLogId"]!, + contactId: data["contactId"], + name: data["name"]); + + // Mark the notification as clicked in the repository. + await _repository.click(notification); + + // Trigger the onMessageClick callback when the notification is selected. + onMessageClick(message); }); } - /// This method is a handler for new remote messages. - /// Check permissions and add notification to the stream. + /// Handles incoming messages from Firebase and triggers appropriate actions based on the content. /// - /// [message] - RemoteMessage object. + /// [message] - The incoming [RemoteMessage] from Firebase. Future onMessage(RemoteMessage message) async { + // Log if no data is provided in the notification message. if (message.data.isEmpty) { if (kDebugMode) { print("Data is not defined: $message"); } } - final notification = DataPayload.fromMap(message.toMap()); + // Parse the message into a notification entity. + final notification = NotificationEntity.fromMap(message.toMap()); + + // Mark the notification as received. + _repository.received(notification); + + // Check if the user has granted permission for notifications. final messagingAllowed = await _checkPermissions(); - PackageInfo packageInfo = await PackageInfo.fromPlatform(); - final appName = packageInfo.appName; - - if (messagingAllowed && notification.details.message.isNotEmpty) { - _repository.didReceiveLocalNotificationStream.add((NotificationEntity( - id: message.hashCode, - notificationId: notification.notification, - title: notification.details.title ?? appName, - body: notification.details.message, - image: notification.details.image, - payload: notification))); + // If allowed and the message has valid details, process and display the notification. + if (messagingAllowed && notification.details?.message != null) { + _repository.didReceiveLocalNotificationStream + .add(notification); // Add to the local stream. } } - /// This method send a notify received push event to Dito + /// Marks a notification as received in the repository. /// - /// [notificationId] - DataPayload object. - Future notifyReceivedNotification(String notificationId) => - _repository.notifyReceivedNotification(notificationId); - - /// This method send a open unsubscribe from notification event to Dito - Future unsubscribeFromNotifications() => - _repository.unsubscribeFromNotifications(); - - /// This method send a open deeplink event to Dito - Future notifyOpenDeepLink(String notificationId) => - _repository.notifyOpenDeepLink(notificationId); - - /// Requests permission to show notifications. - /// - /// Returns a boolean indicating if permission was granted. - Future _checkPermissions() async { - final settings = await FirebaseMessaging.instance.requestPermission(); - return settings.authorizationStatus == AuthorizationStatus.authorized; + /// [notification] - The notification identifier. + /// [notificationLogId] - The dispatch identifier. + /// [contactId] - The contact identifier. + /// [name] - The name of notification. + /// Returns a `Future` that completes with `true` if the event was tracked successfully, + /// or `false` if there was an error. + Future received( + {required String notification, + String? notificationLogId, + String? contactId, + String? name}) async { + try { + return await _repository.received(NotificationEntity( + notification: notification, + notificationLogId: notificationLogId, + contactId: contactId, + name: name)); + } catch (e) { + if (kDebugMode) { + print('Error tracking click event: $e'); // Log the error in debug mode. + } + return false; // Return false if there was an error. + } } - /// This method adds a selected notification to stream + /// Marks a notification as clicked in the repository. /// - /// [data] - DataPayload object. - Future onSelectNotification(DataPayload? data) async { - if (data != null) { - _repository.selectNotificationStream.add(data); + /// [notification] - The notification identifier. + /// [notificationLogId] - The dispatch identifier. + /// [contactId] - The contact identifier. + /// [name] - The name of notification. + /// [createdAt] - The navigation event creation time, defaults to the current UTC time if not provided. + /// Returns a `Future` that completes with `true` if the event was tracked successfully, + /// or `false` if there was an error. + Future click( + {required String notification, + String? notificationLogId, + String? contactId, + String? name, + String? createdAt}) async { + try { + DateTime localDateTime = DateTime.now(); + DateTime utcDateTime = localDateTime.toUtc(); + + return await _repository.click(NotificationEntity( + notification: notification, + notificationLogId: notificationLogId, + contactId: contactId, + name: name, + createdAt: createdAt ?? utcDateTime.toIso8601String(), // Default to current UTC time if not provided. + )); + } catch (e) { + if (kDebugMode) { + print('Error tracking click event: $e'); // Log the error in debug mode. + } + return false; // Return false if there was an error. } } - /// This method registers a mobile token for push notifications. + /// Checks if the user has granted permissions for receiving notifications. /// - /// [token] - The mobile token to be registered. - /// Returns an http.Response. - registryToken(String? token) async { - String? newToken = token ?? await getFirebaseToken(); - if (newToken != null) _repository.registryToken(newToken); + /// Returns `true` if notifications are authorized, `false` otherwise. + Future _checkPermissions() async { + final settings = await FirebaseMessaging.instance.requestPermission(); + return settings.authorizationStatus == AuthorizationStatus.authorized; } - /// This method removes a mobile token for push notifications. + /// Handles notification selection events and triggers appropriate actions. /// - /// [token] - The mobile token to be removed. - /// Returns an http.Response. - removeToken(String? token) async { - String? newToken = token ?? await getFirebaseToken(); - if (newToken != null) _repository.removeToken(newToken); + /// [message] - The selected [RemoteMessage] from Firebase. + void onSelectNotification(RemoteMessage message) { + _repository.selectNotificationStream.add(message); } } diff --git a/package/lib/notification/notification_repository.dart b/package/lib/notification/notification_repository.dart index 57aa5a9..6d83e95 100644 --- a/package/lib/notification/notification_repository.dart +++ b/package/lib/notification/notification_repository.dart @@ -1,108 +1,64 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; -import '../data/dito_api.dart'; -import '../data/notification_database.dart'; -import '../event/event_entity.dart'; -import '../event/event_interface.dart'; -import '../user/user_repository.dart'; +import '../api/dito_api_interface.dart'; import 'notification_entity.dart'; +import '../user/user_repository.dart'; +import '../event/event_dao.dart'; class NotificationRepository { - final DitoApi _api = DitoApi(); - final _database = NotificationEvent(); + final ApiInterface _api = ApiInterface(); final UserRepository _userRepository = UserRepository(); - final EventInterface _eventInterface = EventInterface(); + final _database = EventDAO(); /// The broadcast stream for received notifications final StreamController didReceiveLocalNotificationStream = StreamController.broadcast(); /// The broadcast stream for selected notifications - final StreamController selectNotificationStream = - StreamController.broadcast(); + final StreamController selectNotificationStream = + StreamController.broadcast(); - /// Verifies and processes any pending events. + /// This method send a notify click on push event to Dito /// - /// Throws an exception if the user is not valid. - Future verifyPendingEvents() async { - try { - final events = await _database.fetchAll(); - - for (final event in events) { - final eventName = event["event"] as String; - final token = event["token"] as String; - switch (eventName) { - case "register-token": - registryToken(token); - break; - case "remove-token": - removeToken(token); - break; - default: - break; - } - } - - await _database.clearDatabase(); - } catch (e) { - if (kDebugMode) { - print('Error verifying pending events on notification: $e'); - } - rethrow; - } - } + /// [notification] - Notification object. + Future click(NotificationEntity notification) async { + final activity = ApiActivities().notificationClick(notification); - /// Registers the FCM token with the server. - /// - /// [token] - The FCM token to be registered. - /// Returns an http.Response from the server. - Future registryToken(String token) async { + // If the user is not registered, save the event to the local database if (_userRepository.data.isNotValid) { - return await _database.create('register-token', token); + return await _database.create(EventsNames.click, activity.id, + notification: notification); } - return await _api - .registryToken(token, _userRepository.data) - .then((result) => true) - .catchError((e) => false); - } - - /// Removes the FCM token from the server. - /// - /// [token] - The FCM token to be removed. - /// Returns an http.Response from the server. - Future removeToken(String token) async { - if (_userRepository.data.isNotValid) { - return await _database.create('remove-token', token); + // Otherwise, send the event to the Dito API + try { + return await _api.createRequest([activity]).call(); + } catch (e) { + return await _database.create(EventsNames.click, activity.id, + notification: notification); } - - return await _api - .removeToken(token, _userRepository.data) - .then((result) => true) - .catchError((e) => false); } /// This method send a notify received push event to Dito /// - /// [notification] - DataPayload object. - Future notifyReceivedNotification(String notificationId) async { - await _eventInterface.trackEvent(EventEntity( - eventName: 'received-mobile-push-notification', - customData: {'notification_id': notificationId})); - } + /// [notification] - Notification object. + Future received(NotificationEntity notification) async { + final activity = ApiActivities().notificationReceived(notification); - /// This method send a open unsubscribe from notification event to Dito - Future unsubscribeFromNotifications() async { - await _eventInterface.trackEvent( - EventEntity(eventName: 'unsubscribed-mobile-push-notification')); - } + // If the user is not registered, save the event to the local database + if (_userRepository.data.isNotValid) { + return await _database.create(EventsNames.received, activity.id, + notification: notification); + } - /// This method send a open deeplink event to Dito - Future notifyOpenDeepLink(String? notificationId) async { - await _eventInterface.trackEvent(EventEntity( - eventName: 'open-deeplink-mobile-push-notification', - customData: {'notification_id': notificationId})); + // Otherwise, send the event to the Dito API + try { + return await _api.createRequest([activity]).call(); + } catch (e) { + return await _database.create(EventsNames.received, activity.id, + notification: notification); + } } } diff --git a/package/lib/user/address_entity.dart b/package/lib/user/address_entity.dart new file mode 100644 index 0000000..08dfcb2 --- /dev/null +++ b/package/lib/user/address_entity.dart @@ -0,0 +1,29 @@ +class AddressEntity { + String? city; + String? street; + String? state; + String? postalCode; + String? country; + + AddressEntity( + {this.city, this.street, this.state, this.postalCode, this.country}); + + Map toJson() { + return { + 'city': city, + 'street': street, + 'state': state, + 'postalCode': postalCode, + 'country': country + }; + } + + factory AddressEntity.fromMap(Map map) { + return AddressEntity( + city: map['city'], + street: map['street'], + state: map['state'], + postalCode: map['postalCode'], + country: map['country']); + } +} diff --git a/package/lib/user/token_repository.dart b/package/lib/user/token_repository.dart new file mode 100644 index 0000000..c80c275 --- /dev/null +++ b/package/lib/user/token_repository.dart @@ -0,0 +1,144 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:dito_sdk/user/user_repository.dart'; +import 'package:flutter/foundation.dart'; + +import '../api/dito_api_interface.dart'; +import '../notification/notification_interface.dart'; +import '../proto/api.pb.dart'; +import 'user_dao.dart'; +import 'user_entity.dart'; + +class TokenRepository { + final ApiInterface _api = ApiInterface(); + final UserRepository _userRepository = UserRepository(); + final UserDAO _userDAO = UserDAO(); + final NotificationInterface _notification = NotificationInterface(); + + UserEntity get _userData => _userRepository.data; + + /// Registers the FCM token with the server. + /// + /// [token] - The FCM token to be registered. + /// Returns an http.Response from the server. + Future registryToken([String? token]) async { + if (token == null || token.isEmpty) { + token = await _notification.token; + } + + if (token!.isEmpty && _userData.token != null && _userData.token!.isEmpty) { + throw Exception('User registration token is required'); + } + + if (token.isNotEmpty) _userData.token = token; + + final activity = ApiActivities().registryToken(_userData.token!); + + if (_userData.isNotValid) { + return await _userDAO.create( + UserEventsNames.registryToken, _userData, activity.id); + } + + try { + return await _api.createRequest([activity]).call(); + } catch (e) { + return await _userDAO.create( + UserEventsNames.registryToken, _userData, activity.id); + } + } + + /// Registers the FCM token with the server. + /// + /// [token] - The FCM token to be registered. + /// Returns an http.Response from the server. + Future pingToken([String? token]) async { + if (token == null || token.isEmpty) { + token = await _notification.token; + } + + if (token!.isEmpty && _userData.token != null && _userData.token!.isEmpty) { + throw Exception('User registration token is required'); + } + + if (token.isNotEmpty) _userData.token = token; + + final activity = ApiActivities().pingToken(_userData.token!); + + if (_userData.isNotValid) { + return await _userDAO.create(UserEventsNames.pingToken, _userData, activity.id); + } + + try { + return await _api.createRequest([activity]).call(); + } catch (e) { + return await _userDAO.create(UserEventsNames.pingToken, _userData, activity.id); + } + } + + /// Removes the FCM token from the server. + /// + /// [token] - The FCM token to be removed. + /// Returns an http.Response from the server. + Future removeToken([String? token]) async { + if (token == null || token.isEmpty) { + token = await _notification.token; + } + + if (token!.isEmpty && _userData.token != null && _userData.token!.isEmpty) { + throw Exception('User registration token is required'); + } + + if (_userData.isNotValid) { + throw Exception('User is required'); + } + + if (token.isNotEmpty) _userData.token = token; + + final activity = ApiActivities().removeToken(_userData.token!); + final result = await _api.createRequest([activity]).call(); + + return result; + } + + /// Verifies and processes any pending events. + /// + /// Throws an exception if the user is not valid. + Future verifyPendingEvents() async { + try { + final events = await _userDAO.fetchAll(); + List activities = []; + + for (final event in events) { + final eventName = event["name"] as String; + final user = UserEntity.fromMap(jsonDecode(event["user"] as String)); + final uuid = event["uuid"] as String? ?? null; + final time = event["createdAt"] as String? ?? null; + + switch (eventName) { + case 'registryToken': + activities.add(ApiActivities() + .registryToken(user.token!, uuid: uuid, time: time)); + break; + case 'pingToken': + activities.add( + ApiActivities().pingToken(user.token!, uuid: uuid, time: time)); + break; + default: + break; + } + } + + if (activities.isNotEmpty) { + await _api.createRequest(activities).call(); + } + + return await _userDAO.clearDatabase(); + } catch (e) { + if (kDebugMode) { + print('Error verifying pending events on notification: $e'); + } + rethrow; + } + } +} diff --git a/package/lib/data/notification_database.dart b/package/lib/user/user_dao.dart similarity index 67% rename from package/lib/data/notification_database.dart rename to package/lib/user/user_dao.dart index 188dad3..6eedb3e 100644 --- a/package/lib/data/notification_database.dart +++ b/package/lib/user/user_dao.dart @@ -1,29 +1,41 @@ +import 'dart:convert'; + import 'package:flutter/foundation.dart'; -import 'database.dart'; +import '../data/database.dart'; +import 'user_entity.dart'; + +enum UserEventsNames { login, identify, registryToken, pingToken, removeToken } /// EventDatabaseService is a singleton class that provides methods to interact with a SQLite database /// for storing and managing notification. -class NotificationEvent { +class UserDAO { static final LocalDatabase _database = LocalDatabase(); - static final NotificationEvent _instance = NotificationEvent._internal(); + static final UserDAO _instance = UserDAO._internal(); + get _dataTable => _database.tables["user"]; /// Factory constructor to return the singleton instance of EventDatabaseService. - factory NotificationEvent() { + factory UserDAO() { return _instance; } /// Private named constructor for internal initialization of singleton instance. - NotificationEvent._internal(); + UserDAO._internal(); /// Method to insert a new event into the notification table. /// /// [event] - The event name to be inserted. + /// [user] - The User entity to be inserted. + /// [uuid] - Event Identifier. /// Returns a Future that completes with the row id of the inserted event. - Future create(String event, String token) async { + Future create(UserEventsNames event, UserEntity user, String uuid) async { try { - return await _database.insert(_database.tables["notification"]!, - {'event': event, 'token': token}) > + return await _database.insert(_dataTable!, { + "name": event.name, + "user": jsonEncode(user.toJson()), + "uuid": uuid, + "createdAt": DateTime.now().toIso8601String() + }) > 0; } catch (e) { if (kDebugMode) { @@ -37,12 +49,12 @@ class NotificationEvent { /// /// [event] - The event name to be deleted. /// Returns a Future that completes with the number of rows deleted. - Future delete(String event) async { + Future delete(String userID) async { try { return await _database.delete( - _database.tables["notification"]!, - 'event = ?', - [event], + _dataTable!, + 'userID = ?', + [userID], ) > 0; } catch (e) { @@ -57,7 +69,7 @@ class NotificationEvent { /// Returns a Future that completes with a list of Map objects. Future>> fetchAll() async { try { - return await _database.fetchAll(_database.tables["notification"]!); + return await _database.fetchAll(_dataTable!); } catch (e) { if (kDebugMode) { print('Error retrieving notification: $e'); @@ -70,7 +82,7 @@ class NotificationEvent { /// Returns a Future that completes with the number of rows deleted. Future clearDatabase() async { try { - return _database.clearDatabase(_database.tables["notification"]!); + return _database.clearDatabase(_dataTable!); } catch (e) { if (kDebugMode) { print('Error clearing database: $e'); diff --git a/package/lib/user/user_entity.dart b/package/lib/user/user_entity.dart index 4726a59..11b66fa 100644 --- a/package/lib/user/user_entity.dart +++ b/package/lib/user/user_entity.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'address_entity.dart'; + class UserEntity { String? userID; String? name; @@ -7,7 +9,8 @@ class UserEntity { String? email; String? gender; String? birthday; - String? location; + String? phone; + AddressEntity? address; String? token; Map? customData; @@ -18,7 +21,8 @@ class UserEntity { this.email, this.gender, this.birthday, - this.location, + this.phone, + this.address, this.token, this.customData}); @@ -31,6 +35,9 @@ class UserEntity { // Factory method to instance a user from a JSON object factory UserEntity.fromMap(Map map) { + final address = map['address'] != null + ? AddressEntity.fromMap(map['address'] as Map) + : null; return UserEntity( userID: map['userID'], name: map['name'], @@ -38,7 +45,9 @@ class UserEntity { email: map['email'], gender: map['gender'], birthday: map['birthday'], - location: map['location'], + phone: map['phone'], + token: map['token'], + address: address, customData: map['customData'] != null ? (json.decode(map['customData']) as Map) .map((key, value) => MapEntry(key, value as String)) @@ -53,8 +62,15 @@ class UserEntity { 'email': email, 'gender': gender, 'birthday': birthday, - 'location': location, + 'phone': phone, + 'token': token, + 'address': address?.toJson() ?? {}, 'data': customData != null ? jsonEncode(customData) : null, }; } + + // Factory method to convert a user to Map object + Map toMap() { + return toJson(); + } } diff --git a/package/lib/user/user_interface.dart b/package/lib/user/user_interface.dart index ade4d2c..512192c 100644 --- a/package/lib/user/user_interface.dart +++ b/package/lib/user/user_interface.dart @@ -1,26 +1,87 @@ import 'dart:async'; -import 'package:dito_sdk/notification/notification_repository.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import '../event/event_repository.dart'; import '../utils/custom_data.dart'; +import 'address_entity.dart'; +import 'token_repository.dart'; import 'user_entity.dart'; import 'user_repository.dart'; -/// UserInterface is an interface for communication with the user repository +/// `UserInterface` defines methods for interacting with the user repository, +/// handling user identification and login flows, and managing related tokens. interface class UserInterface { + /// Repository instance for managing user-related operations. final UserRepository _repository = UserRepository(); + + /// Repository instance for managing event-related operations. final EventRepository _eventRepository = EventRepository(); - final NotificationRepository _notificationRepository = - NotificationRepository(); - /// Identifies the user by saving their data and sending it to DitoAPI. + /// Provides access to the current user's data by retrieving it from the repository. + /// + /// Returns a [UserEntity] object representing the user's information. + UserEntity get data => _repository.data; + + /// Provides access to the `TokenRepository` for handling token-related operations. + TokenRepository get token => TokenRepository(); + + /// Identifies the user by saving their data and sending it to Dito. /// - /// [user] - The UserEntity object containing user data. - /// Returns a Future that completes with true if the identification was successful. - Future identify(UserEntity user) async { + /// - [userID] is the required identifier of the user. + /// - Optional parameters like [name], [cpf], [email], [gender], [birthday], etc., + /// allow the specification of additional user details. + /// - [mobileToken] can be passed, or it will be fetched using FirebaseMessaging if not provided. + /// - [customData] allows sending extra information related to the user. + /// + /// Returns a [Future] that completes with `true` if the user identification is successful. + Future identify({ + required String userID, + String? name, + String? cpf, + String? email, + String? gender, + String? birthday, + String? phone, + String? city, + String? street, + String? state, + String? postalCode, + String? country, + String? mobileToken, + Map? customData, + }) async { try { + // Retrieve the mobile token. Use the provided token if available; otherwise, + // fetch it from FirebaseMessaging. + final String userCurrentToken = + mobileToken ?? await FirebaseMessaging.instance.getToken() ?? ""; + + // Create an AddressEntity instance to hold the user's address information. + final address = AddressEntity( + city: city, + street: street, + state: state, + postalCode: postalCode, + country: country, + ); + + // Create a UserEntity instance with the provided user information. + final user = UserEntity( + userID: userID, + name: name, + cpf: cpf, + email: email, + gender: gender, + birthday: birthday, + phone: phone, + address: address, + token: userCurrentToken, + customData: customData, + ); + + // Retrieve any custom data version and merge it with the user's custom data. final version = await customDataVersion; if (user.customData == null) { user.customData = version; @@ -28,13 +89,14 @@ interface class UserInterface { user.customData?.addAll(version); } - final result = _repository.identify(user); - - _eventRepository.verifyPendingEvents(); - _notificationRepository.verifyPendingEvents(); + // Identify the user in the repository and verify any pending events. + final resultIdentify = await _repository.identify(user); + await _eventRepository.verifyPendingEvents(); + await token.verifyPendingEvents(); - return result; + return resultIdentify; } catch (e) { + // Log the error if running in debug mode. if (kDebugMode) { print('Error identifying user: $e'); } @@ -42,8 +104,24 @@ interface class UserInterface { } } - /// Gets the user data from the repository. + /// Logs the user into the system by sending a login event to Dito. /// - /// Returns the UserEntity object containing user data. - UserEntity get data => _repository.data; + /// - [userID] is the required identifier of the user. + /// - [mobileToken] is optional and, if not provided, it will be fetched using FirebaseMessaging. + /// + /// Returns a [Future] that completes with `true` if the login was successful. + Future login({required String userID, String? mobileToken}) async { + try { + final token = await FirebaseMessaging.instance.getToken(); + final user = UserEntity(userID: userID, token: mobileToken ?? token); + // Log the user in through the repository and verify any pending events. + return await _repository.login(user); + } catch (e) { + // Log the error if running in debug mode. + if (kDebugMode) { + print('Error identifying user: $e'); + } + return false; + } + } } diff --git a/package/lib/user/user_repository.dart b/package/lib/user/user_repository.dart index 74bb2db..8fe086e 100644 --- a/package/lib/user/user_repository.dart +++ b/package/lib/user/user_repository.dart @@ -1,7 +1,10 @@ import 'dart:async'; -import '../data/dito_api.dart'; +import 'package:flutter/foundation.dart'; +import '../api/dito_api_interface.dart'; +import '../proto/api.pb.dart'; import 'user_entity.dart'; +import 'user_dao.dart'; final class UserData extends UserEntity { UserData._internal(); @@ -13,7 +16,8 @@ final class UserData extends UserEntity { class UserRepository { final _userData = UserData(); - final DitoApi api = DitoApi(); + final ApiInterface _api = ApiInterface(); + final UserDAO _userDAO = UserDAO(); /// This method get a user data on Static Data Object UserData /// Return a UserEntity Class @@ -24,16 +28,18 @@ class UserRepository { /// This method set a user data on Static Data Object UserData void _set(UserEntity user) { _userData.userID = user.userID; + if (user.phone != null) _userData.phone = user.phone; if (user.cpf != null) _userData.cpf = user.cpf; if (user.name != null) _userData.name = user.name; if (user.email != null) _userData.email = user.email; if (user.gender != null) _userData.gender = user.gender; if (user.birthday != null) _userData.birthday = user.birthday; - if (user.location != null) _userData.location = user.location; + if (user.address != null) _userData.address = user.address; if (user.customData != null) _userData.customData = user.customData; + if (user.token != null) _userData.token = user.token; } - /// This method enable user data save and send to DitoAPI + /// This method enable user data save and send to Dito /// Return bool with true when the identify was successes Future identify(UserEntity? user) async { if (user != null) _set(user); @@ -42,9 +48,65 @@ class UserRepository { throw Exception('User registration id (userID) is required'); } - return await api - .identify(user!) - .then((response) => true) - .catchError((error) => false); + final activity = ApiActivities().identify(); + + try { + return await _api.createRequest([activity]).call(); + } catch (e) { + return await _userDAO.create(UserEventsNames.identify, _userData, activity.id); + } + } + + /// This method enable user data save and send to Dito + /// Return bool with true when the identify was successes + Future login(UserEntity? user) async { + if (user != null) _set(user); + + if (_userData.isNotValid) { + throw Exception('User id (userID) is required'); + } + + final activity = ApiActivities().login(); + + try { + return await _api.createRequest([activity]).call(); + } catch (e) { + return await _userDAO.create(UserEventsNames.login, _userData, activity.id); + } + } + + Future verifyPendingEvents() async { + try { + final events = await _userDAO.fetchAll(); + List activities = []; + + for (final event in events) { + final eventName = event["name"] as String; + final uuid = event["uuid"] as String? ?? null; + final time = event["createdAt"] as String? ?? null; + + switch (eventName) { + case 'identify': + activities.add(ApiActivities().identify(uuid: uuid, time: time)); + break; + case 'login': + activities.add(ApiActivities().login(uuid: uuid, time: time)); + break; + default: + break; + } + } + + if (activities.isNotEmpty) { + await _api.createRequest(activities).call(); + } + + return await _userDAO.clearDatabase(); + } catch (e) { + if (kDebugMode) { + print('Error verifying pending events on notification: $e'); + } + rethrow; + } } } diff --git a/package/lib/utils/custom_data.dart b/package/lib/utils/custom_data.dart index 725b822..e8a90df 100644 --- a/package/lib/utils/custom_data.dart +++ b/package/lib/utils/custom_data.dart @@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart'; /// Returns a Future that completes with a Map containing the version information. Future> get customDataVersion async { try { - return {"dito_sdk_version": "Flutter SDK - 0.5.3"}; + return {"dito_sdk_version": "Flutter SDK - 2.0.0"}; } catch (e) { if (kDebugMode) { print('Error retrieving package info: $e'); diff --git a/package/pubspec.lock b/package/pubspec.lock deleted file mode 100644 index 2e30b24..0000000 --- a/package/pubspec.lock +++ /dev/null @@ -1,498 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _flutterfire_internals: - dependency: transitive - description: - name: _flutterfire_internals - sha256: b46f62516902afb04befa4b30eb6a12ac1f58ca8cb25fb9d632407259555dd3d - url: "https://pub.dev" - source: hosted - version: "1.3.39" - args: - dependency: transitive - description: - name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" - url: "https://pub.dev" - source: hosted - version: "2.5.0" - 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" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" - collection: - dependency: transitive - description: - name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.dev" - source: hosted - version: "1.18.0" - crypto: - dependency: "direct main" - description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab - url: "https://pub.dev" - source: hosted - version: "3.0.3" - dbus: - dependency: transitive - description: - name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" - url: "https://pub.dev" - source: hosted - version: "0.7.10" - device_info_plus: - dependency: "direct main" - description: - name: device_info_plus - sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91 - url: "https://pub.dev" - source: hosted - version: "10.1.0" - device_info_plus_platform_interface: - dependency: transitive - description: - name: device_info_plus_platform_interface - sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 - url: "https://pub.dev" - source: hosted - version: "7.0.0" - event_bus: - dependency: "direct main" - description: - name: event_bus - sha256: "44baa799834f4c803921873e7446a2add0f3efa45e101a054b1f0ab9b95f8edc" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - ffi: - dependency: transitive - description: - name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - file: - dependency: transitive - description: - name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - firebase_core: - dependency: "direct main" - description: - name: firebase_core - sha256: "5159984ce9b70727473eb388394650677c02c925aaa6c9439905e1f30966a4d5" - url: "https://pub.dev" - source: hosted - version: "3.2.0" - firebase_core_platform_interface: - dependency: transitive - description: - name: firebase_core_platform_interface - sha256: "1003a5a03a61fc9a22ef49f37cbcb9e46c86313a7b2e7029b9390cf8c6fc32cb" - url: "https://pub.dev" - source: hosted - version: "5.1.0" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - sha256: "23509cb3cddfb3c910c143279ac3f07f06d3120f7d835e4a5d4b42558e978712" - url: "https://pub.dev" - source: hosted - version: "2.17.3" - firebase_messaging: - dependency: "direct main" - description: - name: firebase_messaging - sha256: "156c4292aa63a6a7d508c68ded984cb38730d2823c3265e573cb1e94983e2025" - url: "https://pub.dev" - source: hosted - version: "15.0.3" - firebase_messaging_platform_interface: - dependency: transitive - description: - name: firebase_messaging_platform_interface - sha256: "10408c5ca242b7fc632dd5eab4caf8fdf18ebe88db6052980fa71a18d88bd200" - url: "https://pub.dev" - source: hosted - version: "4.5.41" - firebase_messaging_web: - dependency: transitive - description: - name: firebase_messaging_web - sha256: c7a756e3750679407948de665735e69a368cb902940466e5d68a00ea7aba1aaa - url: "https://pub.dev" - source: hosted - version: "3.8.11" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" - url: "https://pub.dev" - source: hosted - version: "4.0.0" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - sha256: "40e6fbd2da7dcc7ed78432c5cdab1559674b4af035fddbfb2f9a8f9c2112fcef" - url: "https://pub.dev" - source: hosted - version: "17.1.2" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" - url: "https://pub.dev" - source: hosted - version: "4.0.0+1" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7" - url: "https://pub.dev" - source: hosted - version: "7.1.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" - http: - dependency: "direct main" - description: - name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" - url: "https://pub.dev" - source: hosted - version: "10.0.4" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" - url: "https://pub.dev" - source: hosted - version: "3.0.3" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.dev" - source: hosted - version: "4.0.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" - url: "https://pub.dev" - source: hosted - version: "0.8.0" - meta: - dependency: transitive - description: - name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" - url: "https://pub.dev" - source: hosted - version: "1.12.0" - package_info_plus: - dependency: "direct main" - description: - name: package_info_plus - sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 - url: "https://pub.dev" - source: hosted - version: "8.0.0" - package_info_plus_platform_interface: - dependency: transitive - description: - name: package_info_plus_platform_interface - sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e - url: "https://pub.dev" - source: hosted - version: "3.0.0" - path: - dependency: transitive - description: - name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.dev" - source: hosted - version: "1.9.0" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 - url: "https://pub.dev" - source: hosted - version: "6.0.2" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - sqflite: - dependency: "direct main" - description: - name: sqflite - sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d - url: "https://pub.dev" - source: hosted - version: "2.3.3+1" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" - url: "https://pub.dev" - source: hosted - version: "2.5.4" - sqflite_common_ffi: - dependency: "direct main" - description: - name: sqflite_common_ffi - sha256: "4d6137c29e930d6e4a8ff373989dd9de7bac12e3bc87bce950f6e844e8ad3bb5" - url: "https://pub.dev" - source: hosted - version: "2.3.3" - sqlite3: - dependency: transitive - description: - name: sqlite3 - sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295 - url: "https://pub.dev" - source: hosted - version: "2.4.3" - 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" - synchronized: - dependency: transitive - description: - name: synchronized - sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" - 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: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" - url: "https://pub.dev" - source: hosted - version: "0.7.0" - timezone: - dependency: transitive - description: - name: timezone - sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5 - url: "https://pub.dev" - source: hosted - version: "0.9.3" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" - url: "https://pub.dev" - source: hosted - version: "14.2.1" - web: - dependency: transitive - description: - name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" - url: "https://pub.dev" - source: hosted - version: "0.5.1" - win32: - dependency: transitive - description: - name: win32 - sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 - url: "https://pub.dev" - source: hosted - version: "5.5.1" - win32_registry: - dependency: transitive - description: - name: win32_registry - sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb" - url: "https://pub.dev" - source: hosted - version: "1.1.3" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d - url: "https://pub.dev" - source: hosted - version: "1.0.4" - xml: - dependency: transitive - description: - name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" - source: hosted - version: "6.5.0" -sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.19.0" diff --git a/package/pubspec.yaml b/package/pubspec.yaml index f5412cf..518cab1 100644 --- a/package/pubspec.yaml +++ b/package/pubspec.yaml @@ -1,6 +1,6 @@ name: dito_sdk description: A Flutter package by Dito that enables user registration and user event tracking. -version: 0.5.5 +version: 2.0.0 homepage: https://github.com/ditointernet/sdk_mobile_flutter environment: @@ -20,8 +20,12 @@ dependencies: sqflite_common_ffi: ">=2.3.3" package_info_plus: ">=4.1.0" event_bus: ^2.0.0 + uuid: ^4.4.2 + protobuf: ^3.1.0 + dev_dependencies: flutter_test: sdk: flutter flutter_lints: ">=2.0.0" + pub_updater: ">=0.5.0" diff --git a/package/test/event/database_test.dart b/package/test/event/database_test.dart index 5962266..6b7cf79 100644 --- a/package/test/event/database_test.dart +++ b/package/test/event/database_test.dart @@ -1,4 +1,4 @@ -import 'package:dito_sdk/data/database.dart'; +import 'package:dito_sdk/event/event_dao.dart'; import 'package:dito_sdk/event/event_entity.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; @@ -10,73 +10,68 @@ void main() { }); group('EventDatabaseService Tests', () { - final LocalDatabase eventDatabaseService = LocalDatabase(); - - setUp(() async { - await eventDatabaseService.database; - }); + final EventDAO eventDAO = EventDAO(); tearDown(() async { - await eventDatabaseService.clearDatabase("events"); - await eventDatabaseService.closeDatabase(); + await eventDAO.clearDatabase(); + await eventDAO.closeDatabase(); }); test('should insert an event', () async { final event = EventEntity( - eventName: 'Test Event', - eventMoment: '2024-06-01T12:34:56Z', + action: 'Test Event', + createdAt: '2024-06-01T12:34:56Z', revenue: 100.0, customData: {'key': 'value'}, - ).toMap(); + ); - final success = await eventDatabaseService.insert("events", event); + final success = await eventDAO.create(EventsNames.track, event: event); expect(success, true); - final events = await eventDatabaseService.fetchAll("events"); + final events = await eventDAO.fetchAll(); expect(events.length, 1); - expect(events.first["eventName"], 'Test Event'); + expect(events.first["action"], 'Test Event'); }); test('should delete an event', () async { final event = EventEntity( - eventName: 'Test Event', - eventMoment: '2024-06-01T12:34:56Z', + action: 'Test Event', + createdAt: '2024-06-01T12:34:56Z', revenue: 100.0, customData: {'key': 'value'}, - ).toMap(); + ); - await eventDatabaseService.insert("events", event); - await eventDatabaseService.clearDatabase("events"); + await eventDAO.create(EventsNames.track, event: event); + await eventDAO.clearDatabase(); - final events = await eventDatabaseService.fetchAll("events"); + final events = await eventDAO.fetchAll(); expect(events.isEmpty, true); }); test('should fetch all events', () async { final event1 = EventEntity( - eventName: 'Test Event 1', - eventMoment: '2024-06-01T12:34:56Z', + action: 'Test Event 1', + createdAt: '2024-06-01T12:34:56Z', revenue: 100.0, customData: {'key': 'value1'}, - ).toMap(); + ); final event2 = EventEntity( - eventName: 'Test Event 2', - eventMoment: '2024-06-02T12:34:56Z', + action: 'Test Event 2', + createdAt: '2024-06-02T12:34:56Z', revenue: 200.0, customData: {'key': 'value2'}, - ).toMap(); + ); - await eventDatabaseService.insert("events", event1); - await eventDatabaseService.insert("events", event2); + await eventDAO.create(EventsNames.track, event: event1); + await eventDAO.create(EventsNames.track, event: event2); - final events = await eventDatabaseService.fetchAll("events"); + final events = await eventDAO.fetchAll(); expect(events.length, 2); - expect(events.first["eventName"], 'Test Event 1'); - expect(events.last["eventName"], 'Test Event 2'); + expect(events.first["action"], 'Test Event 1'); + expect(events.last["action"], 'Test Event 2'); }); - }); } diff --git a/package/test/event/event_test.dart b/package/test/event/event_test.dart index 35fd7d1..6afb7ff 100644 --- a/package/test/event/event_test.dart +++ b/package/test/event/event_test.dart @@ -1,7 +1,5 @@ import 'package:dito_sdk/data/database.dart'; import 'package:dito_sdk/dito_sdk.dart'; -import 'package:dito_sdk/event/event_entity.dart'; -import 'package:dito_sdk/user/user_entity.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; @@ -33,17 +31,15 @@ void main() async { }); test('Send event without identify', () async { - await dito.event - .trackEvent(EventEntity(eventName: 'event-test-sdk-flutter')); + await dito.event.track(action: 'event-test-sdk-flutter'); final events = await database.fetchAll("events"); expect(events.length, 1); expect(events.first["eventName"], 'event-test-sdk-flutter'); }); test('Send event with identify', () async { - dito.user.identify(UserEntity(userID: id, email: "teste@teste.com")); - final result = await dito.event - .trackEvent(EventEntity(eventName: 'event-test-sdk-flutter')); + dito.user.identify(userID: id, email: "teste@teste.com"); + final result = await dito.event.track(action: 'event-test-sdk-flutter'); final events = await database.fetchAll("events"); expect(events.length, 0); @@ -51,13 +47,14 @@ void main() async { }); test('Send event with custom data', () async { - dito.user.identify(UserEntity(userID: id, email: "teste@teste.com")); - final result = await dito.event.trackEvent(EventEntity( - eventName: 'event-test-sdk-flutter', + dito.user.identify(userID: id, email: "teste@teste.com"); + final result = await dito.event.track( + action: 'event-test-sdk-flutter', customData: { "data do ultimo teste": DateTime.now().toIso8601String() }, - revenue: 10)); + revenue: 10); + final events = await database.fetchAll("events"); expect(events.length, 0); diff --git a/package/test/user/user_test.dart b/package/test/user/user_test.dart index b051765..30c7d6d 100644 --- a/package/test/user/user_test.dart +++ b/package/test/user/user_test.dart @@ -1,5 +1,4 @@ import 'package:dito_sdk/dito_sdk.dart'; -import 'package:dito_sdk/user/user_entity.dart'; import 'package:flutter_test/flutter_test.dart'; import '../utils.dart'; @@ -19,15 +18,14 @@ void main() { }); test('Set User on memory', () async { - await dito.user - .identify(UserEntity(userID: id, email: "teste@teste.com")); + await dito.user.identify(userID: id, email: "teste@teste.com"); expect(dito.user.data.id, id); expect(dito.user.data.email, "teste@teste.com"); }); test('Send identify', () async { - final result = await dito.user.identify( - UserEntity(userID: "11111111111", email: "teste@teste.com")); + final result = await dito.user + .identify(userID: "11111111111", email: "teste@teste.com"); expect(result, true); expect(dito.user.data.id, "11111111111"); }); diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index 2048d15..0000000 --- a/pubspec.lock +++ /dev/null @@ -1,325 +0,0 @@ -# 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: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" - url: "https://pub.dev" - source: hosted - version: "2.5.0" - 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: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 - url: "https://pub.dev" - source: hosted - version: "0.4.1" - 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: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - 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: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - 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: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - melos: - dependency: "direct dev" - description: - name: melos - sha256: f9a6fc4f4842b7edfca2e00ab3b5b06928584f24bdc3d776ab0b30be7d599450 - url: "https://pub.dev" - source: hosted - version: "6.0.0" - meta: - dependency: transitive - description: - name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 - url: "https://pub.dev" - source: hosted - version: "1.15.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: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.dev" - source: hosted - version: "1.9.0" - platform: - dependency: transitive - description: - name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" - url: "https://pub.dev" - source: hosted - version: "3.1.4" - 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: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" - url: "https://pub.dev" - source: hosted - version: "5.0.2" - 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: "54e8dc865349059ebe7f163d6acce7c89eb958b8047e6d6e80ce93b13d7c9e60" - url: "https://pub.dev" - source: hosted - version: "0.4.0" - 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: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794" - url: "https://pub.dev" - source: hosted - version: "0.7.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" - web: - dependency: transitive - description: - name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" - url: "https://pub.dev" - source: hosted - version: "0.5.1" - 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: e9c1a3543d2da0db3e90270dbb1e4eebc985ee5e3ffe468d83224472b2194a5f - url: "https://pub.dev" - source: hosted - version: "2.2.1" -sdks: - dart: ">=3.3.0 <4.0.0"