diff --git a/.gitignore b/.gitignore index d0bfcf6..c179e32 100644 --- a/.gitignore +++ b/.gitignore @@ -28,5 +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/README.md b/README.md index b9bc54a..8284afe 100644 --- a/README.md +++ b/README.md @@ -1,287 +1,181 @@ -# Dito SDK (Flutter) +[![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/ditointernet/sdk_mobile_flutter/blob/main/README.md) +[![pt-br](https://img.shields.io/badge/lang-pt--br-green.svg)](https://github.com/ditointernet/sdk_mobile_flutter/blob/main/README.pt-br.md) -DitoSDK é uma biblioteca Dart que fornece métodos para integrar aplicativos com a plataforma da Dito. Ela permite identificar usuários, registrar eventos e enviar dados personalizados. +# Dito SDK (Flutter) -## Instalação +DitoSDK is a Dart library that provides methods to integrate applications with the Dito platform. It allows to identify users, record events, and send personalized data. -Para instalar a biblioteca DitoSDK em seu aplicativo Flutter, você deve seguir as instruções fornecidas [nesse link](https://pub.dev/packages/dito_sdk/install). +## Installation -## Métodos +To install the DitoSDK library in your Flutter application, you must follow the instructions provided at [this link](https://pub.dev/packages/dito_sdk/install). -### initialize() +## Entities -Este método deve ser chamado antes de qualquer outra operação com o SDK. Ele inicializa as chaves de API e SECRET necessárias para a autenticação na plataforma Dito. +### UserEntity ```dart -void initialize({required String apiKey, required String secretKey}); +class UserEntity { + String? userID; + String? name; + String? cpf; + String? email; + String? gender; + String? birthday; + String? location; + Map? customData; +} ``` -#### Parâmetros +### DataPayload -- **apiKey** _(String, obrigatório)_: A chave de API da plataforma Dito. -- **secretKey** _(String, obrigatório)_: O segredo da chave de API da plataforma Dito. +```dart +class Details { + final String? link; + final String message; + final String? title; +} -### initializePushNotificationService() +class DataPayload { + final String reference; + final String identifier; + final String? notification; + final String? notification_log_id; + final Details details; +} +``` + +## Methods -Este método deve ser chamado após a inicialização da SDK. Ele inicializa as configurações e serviços necessários para o funcionamento de push notifications da plataforma Dito. +### initialize() + +This method must be called before any other operation with the SDK. It initializes the API and SECRET keys required for authentication on the Dito platform. ```dart -void initializePushNotificationService(); +void initialize({required String apiKey, required String secretKey}); ``` -#### Parâmetros +#### Parameters -- **apiKey** _(String, obrigatório)_: A chave de API da plataforma Dito. -- **secretKey** _(String, obrigatório)_: O segredo da chave de API da plataforma Dito. +- **apiKey** _(String, required)_: The API key for the Dito platform. +- **secretKey** _(String, required)_: The secret key for the Dito platform. -### identify() +### initializePushNotificationService() -Este método define o ID do usuário que será usado para todas as operações subsequentes. +This method should be called after the SDK initialization. It initializes the settings and services necessary for the functioning of push notifications on the Dito platform. ```dart -void identify(String userId); +void initializePushNotificationService(); ``` -- **userID** _(String, obrigatório)_: Id para identificar o usuário na plataforma da Dito. -- **name** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **email** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **gender** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **birthday** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **location** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **customData** _(Map)_: Parâmetro para identificar o usuário na plataforma da Dito. - - -#### identifyUser() +### identify() -Este método registra o usuário na plataforma da Dito com as informações fornecidas anteriormente usando o método `identify()`. +This method defines user settings that will be used for all subsequent operations. ```dart -Future identifyUser() async; +void identify(UserEntity user); ``` -#### Exception +#### Parameters -- Caso a SDK ainda não tenha `userId` cadastrado quando esse método for chamado, irá ocorrer um erro no aplicativo. (utilize o método `setUserId()` para definir o `userId`) +- **user** _(UserEntity, obrigatório)_: Parameter to identify the user on Dito’s platform. ### trackEvent() -O método `trackEvent()` tem a finalidade de registrar um evento na plataforma da Dito. Caso o userID já tenha sido registrado, o evento será enviado imediatamente. No entanto, caso o userID ainda não tenha sido registrado, o evento será armazenado localmente e posteriormente enviado quando o userID for registrado por meio do método `setUserId()`. +This method records an event on the Dito platform. If the user has already been registered, the event will be sent immediately. However, if the user has not yet been registered, the event will be stored locally and sent later. ```dart Future trackEvent({ required String eventName, double? revenue, Map? customData, -}) async; +}); ``` -#### Parâmetros +#### Parameters -- **eventName** _(String, obrigatório)_: O nome do evento a ser registrado. -- **revenue** _(double, opcional)_: A receita associada ao evento. -- **customData** _(Map, opcional)_: Dados personalizados adicionais associados ao evento. +- **eventName** _(String, required)_: Name of the event to be tracked. +- **revenue** _(double)_: Revenue generated from the event. +- **customData** _(Map registryMobileToken({ - required String token, - String? platform, -}); +Future setOnMessageClick( + Function(DataPayload) onMessageClicked +); ``` -#### Parâmetros +#### Parameters -- **token** _(String, obrigatório)_: O token mobile que será registrado. -- **platform** _(String, opcional)_: Nome da plataforma que o usuário está acessando o aplicativo. Valores válidos: 'iPhone' e 'Android'. - `
`_Caso não seja passado algum valor nessa prop, a sdk irá pegar por default o valor pelo `platform`._ +- **onMessageClicked** _(Function(DataPayload), required)_: Function that will be called when clicking on the message -#### Exception -- Caso seja passado um valor diferente de 'iPhone' ou 'Android' na propriedade platform, irá ocorrer um erro no aplicativo. -- Caso a SDK ainda não tenha `identify` cadastrado quando esse método for chamado, irá ocorrer um erro no aplicativo. (utilize o método `identify()` para definir o id do usuário) +## Token management -### removeMobileToken() +Our SDK ensures the registration of the current user's token in addition to the deletion of invalid tokens. However, we also provide the following methods in case you need to add/remove any token. -Este método permite remover um token mobile para o usuário. +### registryMobileToken() + +This method allows you to register a mobile token for the user. ```dart -Future removeMobileToken({ - required String token, - String? platform, +Future registryToken({ + String? token, }); ``` #### Parâmetros -- **token** _(String, obrigatório)_: O token mobile que será removido. -- **platform** _(String, opcional)_: Nome da plataforma que o usuário está acessando o aplicativo. Valores válidos: 'iPhone' e 'Android'. - `
`_Caso não seja passado algum valor nessa prop, a sdk irá pegar por default o valor pelo `platform`._ +- **token** _(String)_: The mobile token that will be registered, if it is not sent we will get the value from Firebase. #### Exception -- Caso seja passado um valor diferente de 'iPhone' ou 'Android' na propriedade platform, irá ocorrer um erro no aplicativo. -- Caso a SDK ainda não tenha `identify` cadastrado quando esse método for chamado, irá ocorrer um erro no aplicativo. (utilize o método `identify()` para definir o id do usuário) - -### openNotification() +- If the SDK does not already have `user` registered when this method is called, an error will occur in the application. (Use the `identify()` method to define the user) +### removeMobileToken() -Este método permite registrar a abertura de uma notificação mobile. +This method allows you to remove a mobile token for the user. ```dart -Future openNotification({ - required String notificationId, - required String identifier, - required String reference -}) async +Future removeMobileToken({ + String? token, +}); ``` -#### Parâmetros - -- **notificationId** _(String, obrigatório)_: Id da notificação da Dito recebida pelo aplicativo. -- **identifier** _(String, obrigatório)_: Parâmetro para dentificar a notificação na plataforma da Dito. -- **reference** _(String, obrigatório)_: Parâmetro para identificar o usuário na plataforma da Dito. - -###### Observações +#### Parameters -- Esses parâmetros estarão presentes no data da notificação +- **token** _(String)_: The mobile token that will be removed, if it is not sent we will get the value from Firebase. -## Classes - -### User - -Classe para manipulação dos dados do usuário. - -```dart -User user = User( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); -``` - -#### Parâmetros +#### Exception -- **userID** _(String, obrigatório)_: Id para identificar o usuário na plataforma da Dito. -- **name** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **email** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **gender** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **birthday** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **location** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **customData** _(Map)_: Parâmetro para identificar o usuário na plataforma da Dito. +- If the SDK does not already have `user` registered when this method is called, an error will occur in the application. (Use the `identify()` method to define the user) -## Exemplos +## Examples -### Uso básico da SDK: +### Using the SDK only for event tracking: ```dart import 'package:dito_sdk/dito_sdk.dart'; final dito = DitoSDK(); -// Inicializa a SDK com suas chaves de API -dito.initialize(apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); +// Initialize the SDK with your API keys +dito.initialize( apiKey: 'your_api_key', secretKey: 'your_secret_key'); -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); +// Sets or updates user information on the instance +final user = UserEntity(userID: cpf, cpf: cpf, name: name, email: email); +await dito.identify(user); -// Envia as informações do usuário (que foram definidas ou atualizadas pelo identify) para a Dito -await dito.identifyUser(); -// Registra um evento na Dito +// Register an event at Dito await dito.trackEvent(eventName: 'login'); ``` -### Uso avançado da SDK: - -#### main.dart +### Using the SDK for push notification: -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Inicializa a SDK com suas chaves de API -dito.initialize(apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); -``` - -#### login.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Define o ID do usuário -dito.setUserId('id_do_usuario'); -dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); -await dito.identifyUser(); -``` - -#### arquivoX.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); -await dito.identifyUser(); -await dito.registryMobileToken(token: token); - -``` - -#### arquivoY.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'Rio de Janeiro', { - 'loja preferida': 'LojaX', - 'canal preferido': 'Loja Física' -}); -await dito.identifyUser(); -``` - -Isso resultará no envio do seguinte payload do usuário ao chamar `identifyUser()`: - -```javascript -{ - name: 'João da Silva', - email: 'joao@example.com', - location: 'Rio de Janeiro', - customData: { - 'loja preferida': 'LojaX', - 'canal preferido': 'Loja Física' - } -} -``` - -A nossa SDK é uma instância única, o que significa que, mesmo que ela seja inicializada em vários arquivos ou mais de uma vez, ela sempre referenciará as mesmas informações previamente armazenadas. Isso nos proporciona a flexibilidade de chamar o método `identify()` a qualquer momento para adicionar ou atualizar os detalhes do usuário, e somente quando necessário, enviá-los através do método `identifyUser()`. - -#### arquivoZ.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Registra um evento na Dito -dito.trackEvent( - eventName: 'comprou produto', - revenue: 99.90, - customData: { - 'produto': 'produtoX', - 'sku_produto': '99999999', - 'metodo_pagamento': 'Visa', - }, -); -``` - -### Uso da SDK com push notification: - -Para o funcionamento é necessário configurar a lib do Firebase Cloud Message (FCM), seguindo os seguintes passos: +For it to work, you need to configure the Firebase Cloud Message (FCM) lib, following the +following steps: ```shell dart pub global activate flutterfire_cli @@ -292,34 +186,31 @@ flutter pub add firebase_core firebase_messaging flutterfire configure ``` -Siga os passos que irá aparecer na CLI, assim terá as chaves de acesso do Firebase configuradas dentro dos App's Android e iOS. +Follow the steps that will appear in the CLI, so you will have the Firebase access keys configured +within the Android and iOS Apps. #### main.dart + ```dart import 'package:dito_sdk/dito_sdk.dart'; -// Método para registrar um serviço que irá receber os push quando o app estiver totalmente fechado +// Method to register a service that will receive messages when the app is completely closed or in the background @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); - - dito.notificationService().showLocalNotification(CustomNotification( - id: message.hashCode, - title: notification.details.title || "O nome do aplicativo", - body: notification.details.message, - payload: notification)); + DitoSDK dito = DitoSDK(); + dito.onBackgroundMessageHandler(message, + apiKey: Constants.ditoApiKey, secretKey: Constants.ditoSecretKey); } void main() async { WidgetsFlutterBinding.ensureInitialized(); - - await Firebase.initializeApp(); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); DitoSDK dito = DitoSDK(); - dito.initialize(apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); + dito.initialize(apiKey: 'your_api_key', secretKey: 'your_secret_key'); await dito.initializePushService(); } ``` -> Lembre-se de substituir 'sua_api_key', 'sua_secret_key' e 'id_do_usuario' pelos valores corretos em seu ambiente. +> Remember to replace 'your_api_key', 'your_secret_key' with the correct values +> in your environment. diff --git a/README.pt-br.md b/README.pt-br.md new file mode 100644 index 0000000..d25a7ac --- /dev/null +++ b/README.pt-br.md @@ -0,0 +1,224 @@ +# Dito SDK (Flutter) + +DitoSDK é uma biblioteca Dart que fornece métodos para integrar aplicativos com a plataforma da +Dito. Ela permite identificar usuários, registrar eventos e enviar dados personalizados. + +## Instalação + +Para instalar a biblioteca DitoSDK em seu aplicativo Flutter, você deve seguir as instruções +fornecidas [nesse link](https://pub.dev/packages/dito_sdk/install). + +## Entidades + +### UserEntity + +```dart +class UserEntity { + String? userID; + String? name; + String? cpf; + String? email; + String? gender; + String? birthday; + String? location; + Map? customData; +} +``` + +### DataPayload + +```dart +class Details { + final String? link; + final String message; + final String? title; +} + +class DataPayload { + final String reference; + final String identifier; + final String? notification; + final String? notification_log_id; + final Details details; +} +``` + +## Métodos + +### initialize() + +Este método deve ser chamado antes de qualquer outra operação com o SDK. Ele inicializa as chaves de +API e SECRET necessárias para a autenticação na plataforma Dito. + +```dart +void initialize({required String apiKey, required String secretKey}); +``` + +#### Parâmetros + +- **apiKey** _(String, obrigatório)_: A chave de API da plataforma Dito. +- **secretKey** _(String, obrigatório)_: O segredo da chave de API da plataforma Dito. + +### initializePushNotificationService() + +Este método deve ser chamado após a inicialização da SDK. Ele inicializa as configurações e serviços +necessários para o funcionamento de push notifications da plataforma Dito. + +```dart +void initializePushNotificationService(); +``` + +### identify() + +Este método define as configurações do usuário que será usado para todas as operações subsequentes. + +```dart +void identify(UserEntity user); +``` + +#### Parameters + +- **user** _(UserEntity, obrigatório)_: Parâmetro para identificar o usuário na plataforma da Dito. + +### trackEvent() + +O método `trackEvent()` tem a finalidade de registrar um evento na plataforma da Dito. Caso o usuário +já tenha sido registrado, o evento será enviado imediatamente. No entanto, caso o usuário ainda não +tenha sido registrado, o evento será armazenado localmente e posteriormente enviado quando o usuário +for registrado por meio do método `identify()`. + +```dart +Future trackEvent({ + required String eventName, + double? revenue, + Map? customData, +}); +``` + +#### Parâmetros + +- **eventName** _(String, obrigatório)_: O nome do evento a ser registrado. +- **revenue** _(double, opcional)_: A receita associada ao evento. +- **customData** _(Map, opcional)_: Dados personalizados adicionais associados ao + evento. + +### setOnMessageClick() + +O método `setOnMessageClick()` configura uma callback para o evento de clique na notificação push. + +```dart +Future setOnMessageClick( + Function(DataPayload) onMessageClicked +); +``` + +#### Parâmetros + +- **onMessageClicked** _(Function(DataPayload), obrigatório)_: Função que será chamada ao clicar na mensagem + + +## Gerenciamento de tokens + +A nossa SDK garante o registro do token atual do usuário além da deleção dos tokens inválidos. Mas também disponibilizamos os métodos a seguir caso necessite de adicionar/remover algum token. + +### registryMobileToken() + +Este método permite registrar um token mobile para o usuário. + +```dart +Future registryToken({ + String? token, +}); +``` + +#### Parâmetros + +- **token** _(String)_: O token mobile que será registrado, caso não seja enviado pegamos o valor do Firebase. + +#### Exception + +- Caso a SDK ainda não tenha `user` cadastrado quando esse método for chamado, irá ocorrer um + erro no aplicativo. (utilize o método `identify()` para definir o usuário) + +### removeMobileToken() + +Este método permite remover um token mobile para o usuário. + +```dart +Future removeMobileToken({ + String? token, +}); +``` + +#### Parâmetros + +- **token** _(String)_: O token mobile que será removido, caso não seja enviado pegamos o valor do Firebase. + +#### Exception + +- Caso a SDK ainda não tenha `user` cadastrado quando esse método for chamado, irá ocorrer um + erro no aplicativo. (utilize o método `identify()` para definir o usuário) + +## Exemplos + +### Uso da SDK somente com tracking de eventos: + +```dart +import 'package:dito_sdk/dito_sdk.dart'; + +final dito = DitoSDK(); + +// Inicializa a SDK com suas chaves de API +dito.initialize( apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); + +// Define ou atualiza informações do usuário na instância +final user = UserEntity(userID: cpf, cpf: cpf, name: name, email: email); +await dito.identify(user); + + +// Registra um evento na Dito +await dito.trackEvent(eventName: 'login'); +``` + +### Uso da SDK com push notification: + +Para o funcionamento é necessário configurar a lib do Firebase Cloud Message (FCM), seguindo os +seguintes passos: + +```shell +dart pub global activate flutterfire_cli +flutter pub add firebase_core firebase_messaging +``` + +```shell +flutterfire configure +``` + +Siga os passos que irá aparecer na CLI, assim terá as chaves de acesso do Firebase configuradas +dentro dos App's Android e iOS. + +#### main.dart + +```dart +import 'package:dito_sdk/dito_sdk.dart'; + +// Método para registrar um serviço que irá receber as mensagens quando o app estiver totalmente fechado ou em segundo plano +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + DitoSDK dito = DitoSDK(); + dito.onBackgroundMessageHandler(message, + apiKey: Constants.ditoApiKey, secretKey: Constants.ditoSecretKey); +} + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + + DitoSDK dito = DitoSDK(); + dito.initialize(apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); + await dito.initializePushService(); +} +``` + +> Lembre-se de substituir 'sua_api_key', 'sua_secret_key' pelos valores corretos +> em seu ambiente. 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/ios/Podfile b/example/ios/Podfile index d97f17e..9e2cb6e 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +platform :ios, '16.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/lib/app.dart b/example/lib/app.dart index ba48a06..597c43c 100644 --- a/example/lib/app.dart +++ b/example/lib/app.dart @@ -37,7 +37,6 @@ class _AppState extends State { ), body: const Center( child: AppForm(), - )) - ); + ))); } } diff --git a/example/lib/app_form.dart b/example/lib/app_form.dart index bebae3f..825d236 100644 --- a/example/lib/app_form.dart +++ b/example/lib/app_form.dart @@ -1,5 +1,4 @@ import 'package:dito_sdk/dito_sdk.dart'; -import 'package:dito_sdk/entity/custom_notification.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -19,52 +18,177 @@ class AppFormState extends State { Widget build(BuildContext context) { final dito = Provider.of(context); - String cpf = "22222222222"; - String email = "teste@dito.com.br"; + String cpf = "33333333333"; + String email = "teste.sdk-flutter@dito.com.br"; - identify() async { - dito.identify( - userID: cpf, cpf: cpf, name: 'Teste SDK Flutter', email: email); - await dito.identifyUser(); + identify() { + return dito.user.identify( + userID: "1575213826e164f73d28c4ed1b5fabaad4bd4a13", + cpf: cpf, + name: 'Usuário SDK FLutter - Teste', + email: email); + } - // final token = await dito.notificationService().getDeviceFirebaseToken(); - // - // if (token != null && token.isNotEmpty) { - // dito.registryMobileToken(token: token); - // } + login() { + return dito.user + .login(userID: '1575213826e164f73d28c4ed1b5fabaad4bd4a13'); } handleIdentify() async { if (_formKey.currentState!.validate()) { - await identify(); + final bool response = await identify(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Usuário identificado')), + 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), + ); + } + } + } + + handleGenericTrack() async { + if (_formKey.currentState!.validate()) { + 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), + ); + } } } - handleNotification() async { + handleReceivedNotification() async { if (_formKey.currentState!.validate()) { - await identify(); - await dito.trackEvent(eventName: 'action-test'); + 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), + ); } } - handleLocalNotification() { - dito.notificationService().addNotificationToStream(CustomNotification( - id: 123, - title: "Notificação local", - body: - "Está é uma mensagem de teste, validando o stream de dados das notificações locais")); + 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), + ); + } + } - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Push enviado para a fila')), - ); + 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( @@ -73,8 +197,8 @@ class AppFormState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ TextFormField( - onSaved: (value) { - cpf = value!; + onChanged: (value) { + cpf = value; }, initialValue: cpf, validator: (value) { @@ -85,8 +209,8 @@ class AppFormState extends State { }, ), TextFormField( - onSaved: (value) { - email = value!; + onChanged: (value) { + email = value; }, initialValue: email, validator: (value) { @@ -101,17 +225,41 @@ class AppFormState extends State { padding: const EdgeInsets.all(15.0), child: Column(children: [ FilledButton( - child: const Text('Registrar Identify'), onPressed: handleIdentify, + 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: handleClickNotification, + child: const Text('Registrar evento de click'), ), OutlinedButton( - child: const Text('Receber Notification'), - onPressed: handleNotification, + 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'), ), - TextButton( - child: const Text('Criar notificação local'), - onPressed: handleLocalNotification, - ) ]))) ], ), diff --git a/example/lib/constants.dart b/example/lib/constants.dart deleted file mode 100644 index f48e316..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: '', - ); -} \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 790ba6f..629b5dd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,47 +1,46 @@ -import 'dart:convert'; - import 'package:dito_sdk/dito_sdk.dart'; -import 'package:dito_sdk/entity/custom_notification.dart'; -import 'package:dito_sdk/entity/data_payload.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:package_info_plus/package_info_plus.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 { - PackageInfo packageInfo = await PackageInfo.fromPlatform(); - final appName = packageInfo.appName; - + await Firebase.initializeApp(); DitoSDK dito = DitoSDK(); - dito.initialize( - apiKey: Constants.ditoApiKey, secretKey: Constants.ditoSecretKey); - await dito.initializePushNotificationService(); - - final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); - - dito.notificationService().showLocalNotification(CustomNotification( - id: message.hashCode, - title: appName, - body: notification.details.message, - payload: notification)); + dito.initialize(apiKey: apiKey, secretKey: secretKey); + dito.onBackgroundPushNotificationHandler(message: message); } void main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp(); - FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); 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); + } + }; + runApp(MultiProvider(providers: [ Provider( create: (context) { diff --git a/example/pubspec.lock b/example/pubspec.lock deleted file mode 100644 index ebf849c..0000000 --- a/example/pubspec.lock +++ /dev/null @@ -1,521 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _flutterfire_internals: - dependency: transitive - description: - name: _flutterfire_internals - sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7" - url: "https://pub.dev" - source: hosted - version: "1.3.35" - 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.1" - 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: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" - url: "https://pub.dev" - source: hosted - version: "2.32.0" - firebase_core_platform_interface: - dependency: transitive - description: - name: firebase_core_platform_interface - sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 - url: "https://pub.dev" - source: hosted - version: "5.0.0" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - sha256: "43d9e951ac52b87ae9cc38ecdcca1e8fa7b52a1dd26a96085ba41ce5108db8e9" - url: "https://pub.dev" - source: hosted - version: "2.17.0" - firebase_messaging: - dependency: "direct main" - description: - name: firebase_messaging - sha256: a1662cc95d9750a324ad9df349b873360af6f11414902021f130c68ec02267c4 - url: "https://pub.dev" - source: hosted - version: "14.9.4" - firebase_messaging_platform_interface: - dependency: transitive - description: - name: firebase_messaging_platform_interface - sha256: "87c4a922cb6f811cfb7a889bdbb3622702443c52a0271636cbc90d813ceac147" - url: "https://pub.dev" - source: hosted - version: "4.5.37" - firebase_messaging_web: - dependency: transitive - description: - name: firebase_messaging_web - sha256: "0d34dca01a7b103ed7f20138bffbb28eb0e61a677bf9e78a028a932e2c7322d5" - url: "https://pub.dev" - source: hosted - version: "3.8.7" - 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 8b5d9fd..7b6df2e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -37,9 +37,9 @@ dependencies: cupertino_icons: ^1.0.6 provider: ^6.1.2 package_info_plus: ^4.2.0 - firebase_core: ^2.30.1 - firebase_messaging: ^14.9.1 - dito_sdk: ^0.5.1 + firebase_core: ^3.1.1 + firebase_messaging: ^15.0.2 + 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/CHANGELOG.md b/package/CHANGELOG.md index 1b89e77..120638d 100644 --- a/package/CHANGELOG.md +++ b/package/CHANGELOG.md @@ -1,3 +1,30 @@ +## 0.5.5 () + +### New Features: + +- User domain implementation; +- Creation of event domain; +- Changes on SDK architecture; +- Change implementation of identify to `DitoSDK.identify(UserEntity(userID: ''))`; + +### Bug Fixes: + +- Hotfix: Changed Data Payload type to string. +- Prevented sending opened notifications with empty ID. + +### Improvements: + +- Version update. +- Configured DitoApi instance. +- Removed unnecessary endpoints and domains. + +### Notable Commits: + +- [NOT-3023] DitoApi +- [NOT-3023] constants +- feat: User domain implementation +- feat: create event domain + ## 0.5.4 (Jun 05, 2024) ### Bug Fixes @@ -31,44 +58,45 @@ ### Bug Fixes - Bug Fixes - - Backward compatibility for dependent packages; - - `openNotification()` in the request submission contract. + - Backward compatibility for dependent packages; + - `openNotification()` in the request submission contract. - Additional Notes - - No additional notes in this version. + - No additional notes in this version. ## 0.5.0 (April 30, 2024) ### New Features - Added the following methods: - - `setUser()`: Method to save user data before an identify. - - `removeMobileToken()`: Method to remove a user's token. - - `initializePushNotificationService()`: Method to initialize the mobile push notification service. - - `setAndroidDetails()`: Method to customize the mobile push notification service on Android. - - `setIosDetails()`: Method to customize the mobile push notification service on iOS. + - `setUser()`: Method to save user data before an identify. + - `removeMobileToken()`: Method to remove a user's token. + - `initializePushNotificationService()`: Method to initialize the mobile push notification + service. + - `setAndroidDetails()`: Method to customize the mobile push notification service on Android. + - `setIosDetails()`: Method to customize the mobile push notification service on iOS. - Changes - - Removed setUserAgent method, which is now generated automatically. - - Deprecated setUserId method. + - Removed setUserAgent method, which is now generated automatically. + - Deprecated setUserId method. - Bug Fixes - - No bug fixes in this version. + - No bug fixes in this version. - Additional Notes - - No additional notes in this version. + - No additional notes in this version. ## 0.4.0 (November 23, 2023) ### New Features - Added the following methods: - - `registryMobileToken()`: Method to register the mobile token for the user. - - `openNotification()`: Method to notify of the opening of a mobile notification. + - `registryMobileToken()`: Method to register the mobile token for the user. + - `openNotification()`: Method to notify of the opening of a mobile notification. - Changes - Removed encoding attribute from all requests. - Removed json.encode from the data attribute in the identifyUser() method. - `identifyUser()` method returning the request result. (changed to Future). - Bug Fixes - - No bug fixes in this version. + - No bug fixes in this version. - Additional Notes - - No additional notes in this version. + - No additional notes in this version. ## 0.3.0 (October 26, 2023) @@ -77,35 +105,35 @@ - Event storage while not having a registered userID. - Sending stored events as soon as a userID is registered. - Changes - - No changes in this version. + - No changes in this version. - Bug Fixes - - No bug fixes in this version. + - No bug fixes in this version. - Additional Notes - - No additional notes in this version. + - No additional notes in this version. ## 0.2.0 (October 10, 2023) ### Refactor - Changes - - Renamed registerUser() method to identifyUser(). - - Documentation improvements. + - Renamed registerUser() method to identifyUser(). + - Documentation improvements. - Bug Fixes - - No bug fixes in this version. + - No bug fixes in this version. - Additional Notes - - No additional notes in this version. + - No additional notes in this version. ## 0.1.1 (October 2, 2023) ### Refectory - Changes - - Removed print from methods. - - Exception handling in methods that contain requests. + - Removed print from methods. + - Exception handling in methods that contain requests. - Bug Fixes - - No bug fixes in this version. + - No bug fixes in this version. - Additional Notes - - No additional notes in this version. + - No additional notes in this version. ## 0.1.0 (October 2, 2023) diff --git a/package/Makefile b/package/Makefile new file mode 100644 index 0000000..b34f41c --- /dev/null +++ b/package/Makefile @@ -0,0 +1,10 @@ +login: + buf registry login + +pull: + buf export buf.build/dito/sdk-service -o proto + buf export buf.build/protocolbuffers/wellknowntypes -o proto + +build: + buf build + buf generate proto --include-imports --clean --debug diff --git a/package/README.md b/package/README.md index 7a5553c..27ea730 100644 --- a/package/README.md +++ b/package/README.md @@ -8,9 +8,44 @@ Dito. Ela permite identificar usuários, registrar eventos e enviar dados person Para instalar a biblioteca DitoSDK em seu aplicativo Flutter, você deve seguir as instruções fornecidas [nesse link](https://pub.dev/packages/dito_sdk/install). +## Entidades + +### UserEntity + +```dart +class UserEntity { + String? userID; + String? name; + String? cpf; + String? email; + String? gender; + String? birthday; + String? location; + Map? customData; +} +``` + +### DataPayload + +```dart +class Details { + final String? link; + final String message; + final String? title; +} + +class DataPayload { + final String reference; + final String identifier; + final String? notification; + final String? notification_log_id; + final Details details; +} +``` + ## Métodos -### initialize() +### initialize() Este método deve ser chamado antes de qualquer outra operação com o SDK. Ele inicializa as chaves de API e SECRET necessárias para a autenticação na plataforma Dito. @@ -24,7 +59,7 @@ void initialize({required String apiKey, required String secretKey}); - **apiKey** _(String, obrigatório)_: A chave de API da plataforma Dito. - **secretKey** _(String, obrigatório)_: O segredo da chave de API da plataforma Dito. -### initializePushNotificationService() +### initializePushNotificationService() Este método deve ser chamado após a inicialização da SDK. Ele inicializa as configurações e serviços necessários para o funcionamento de push notifications da plataforma Dito. @@ -33,66 +68,29 @@ necessários para o funcionamento de push notifications da plataforma Dito. void initializePushNotificationService(); ``` -#### Parâmetros - -- **apiKey** _(String, obrigatório)_: A chave de API da plataforma Dito. -- **secretKey** _(String, obrigatório)_: O segredo da chave de API da plataforma Dito. - ### identify() -Este método define o ID do usuário que será usado para todas as operações subsequentes. - -```dart -void identify(String userId); -``` - -- **userID** _(String, obrigatório)_: Id para identificar o usuário na plataforma da Dito. -- **name** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **email** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **gender** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **birthday** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **location** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **customData** _(Map)_: Parâmetro para identificar o usuário na plataforma da - Dito. - -#### identifyUser() - -Este método registra o usuário na plataforma da Dito com as informações fornecidas anteriormente -usando o método `identify()`. +Este método define as configurações do usuário que será usado para todas as operações subsequentes. ```dart -Future identifyUser - -() async; +void identify(UserEntity user); ``` -#### Exception - -- Caso a SDK ainda não tenha `userId` cadastrado quando esse método for chamado, irá ocorrer um erro - no aplicativo. (utilize o método `setUserId()` para definir o `userId`) +- **user** _(UserEntity, obrigatório)_: Parâmetro para identificar o usuário na plataforma da Dito. ### trackEvent() -O método `trackEvent()` tem a finalidade de registrar um evento na plataforma da Dito. Caso o userID -já tenha sido registrado, o evento será enviado imediatamente. No entanto, caso o userID ainda não -tenha sido registrado, o evento será armazenado localmente e posteriormente enviado quando o userID -for registrado por meio do método `setUserId()`. +O método `trackEvent()` tem a finalidade de registrar um evento na plataforma da Dito. Caso o usuário +já tenha sido registrado, o evento será enviado imediatamente. No entanto, caso o usuário ainda não +tenha sido registrado, o evento será armazenado localmente e posteriormente enviado quando o usuário +for registrado por meio do método `identify()`. ```dart -Future trackEvent -( -{ -required -String -eventName, -double? revenue, -Map -? -customData -, -} -) -async; +Future trackEvent({ + required String eventName, + double? revenue, + Map? customData, +}); ``` #### Parâmetros @@ -102,110 +100,66 @@ async; - **customData** _(Map, opcional)_: Dados personalizados adicionais associados ao evento. -### registryMobileToken() +### setOnMessageClick() -Este método permite registrar um token mobile para o usuário. +O método `setOnMessageClick()` configura uma callback para o evento de clique na notificação push. ```dart -Future registryMobileToken({ - required String token, - String? platform, -}); +Future setOnMessageClick( + Function(DataPayload) onMessageClicked +); ``` #### Parâmetros -- **token** _(String, obrigatório)_: O token mobile que será registrado. -- **platform** _(String, opcional)_: Nome da plataforma que o usuário está acessando o aplicativo. - Valores válidos: 'iPhone' e 'Android'. - `
`_Caso não seja passado algum valor nessa prop, a sdk irá pegar por default o valor - pelo `platform`._ +- **onMessageClicked** _(Function(DataPayload), obrigatório)_: Função que será chamada ao clicar na mensagem -#### Exception -- Caso seja passado um valor diferente de 'iPhone' ou 'Android' na propriedade platform, irá ocorrer - um erro no aplicativo. -- Caso a SDK ainda não tenha `identify` cadastrado quando esse método for chamado, irá ocorrer um - erro no aplicativo. (utilize o método `identify()` para definir o id do usuário) +## Gerenciamento de tokens -### removeMobileToken() +A nossa SDK garante o registro do token atual do usuário além da deleção dos tokens inválidos. Mas também disponibilizamos os métodos a seguir caso necessite de adicionar/remover algum token. -Este método permite remover um token mobile para o usuário. +### registryMobileToken() + +Este método permite registrar um token mobile para o usuário. ```dart -Future removeMobileToken({ - required String token, - String? platform, +Future registryToken({ + String? token, }); ``` #### Parâmetros -- **token** _(String, obrigatório)_: O token mobile que será removido. -- **platform** _(String, opcional)_: Nome da plataforma que o usuário está acessando o aplicativo. - Valores válidos: 'iPhone' e 'Android'. - `
`_Caso não seja passado algum valor nessa prop, a sdk irá pegar por default o valor - pelo `platform`._ +- **token** _(String)_: O token mobile que será registrado, caso não seja enviado pegamos o valor do Firebase. #### Exception -- Caso seja passado um valor diferente de 'iPhone' ou 'Android' na propriedade platform, irá ocorrer - um erro no aplicativo. - Caso a SDK ainda não tenha `identify` cadastrado quando esse método for chamado, irá ocorrer um - erro no aplicativo. (utilize o método `identify()` para definir o id do usuário) + erro no aplicativo. (utilize o método `identify()` para definir o usuário) -### openNotification() +### removeMobileToken() -Este método permite registrar a abertura de uma notificação mobile. +Este método permite remover um token mobile para o usuário. ```dart -Future openNotification -( -{ -required -String -notificationId, -required String identifier, -required String reference -}) async +Future removeMobileToken({ + String? token, +}); ``` #### Parâmetros -- **notificationId** _(String, obrigatório)_: Id da notificação da Dito recebida pelo aplicativo. -- **identifier** _(String, obrigatório)_: Parâmetro para dentificar a notificação na plataforma da - Dito. -- **reference** _(String, obrigatório)_: Parâmetro para identificar o usuário na plataforma da Dito. - -###### Observações - -- Esses parâmetros estarão presentes no data da notificação - -## Classes - -### User - -Classe para manipulação dos dados do usuário. +- **token** _(String)_: O token mobile que será removido, caso não seja enviado pegamos o valor do Firebase. -```dart - -User user = User(sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); -``` - -#### Parâmetros +#### Exception -- **userID** _(String, obrigatório)_: Id para identificar o usuário na plataforma da Dito. -- **name** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **email** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **gender** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **birthday** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **location** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **customData** _(Map)_: Parâmetro para identificar o usuário na plataforma da - Dito. +- Caso a SDK ainda não tenha `identify` cadastrado quando esse método for chamado, irá ocorrer um + erro no aplicativo. (utilize o método `identify()` para definir o usuário) ## Exemplos -### Uso básico da SDK: +### Uso da SDK somente com tracking de eventos: ```dart import 'package:dito_sdk/dito_sdk.dart'; @@ -213,133 +167,15 @@ import 'package:dito_sdk/dito_sdk.dart'; final dito = DitoSDK(); // Inicializa a SDK com suas chaves de API -dito.initialize -( -apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); +dito.initialize( apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); +// Define ou atualiza informações do usuário na instância +final user = UserEntity(userID: cpf, cpf: cpf, name: name, email: email); +await dito.identify(user); -// Envia as informações do usuário (que foram definidas ou atualizadas pelo identify) para a Dito -await dito.identifyUser(); // Registra um evento na Dito -await dito.trackEvent(eventName: ' -login -' -); -``` - -### Uso avançado da SDK: - -#### main.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Inicializa a SDK com suas chaves de API -dito.initialize -( -apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); -``` - -#### login.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Define o ID do usuário -dito.setUserId -('id_do_usuario -' -);dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); -await dito.identifyUser(); -``` - -#### arquivoX.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify -( -sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); -await dito.identifyUser(); -await dito.registryMobileToken( -token -: -token -); - -``` - -#### arquivoY.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify -( -sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'Rio de Janeiro', { -'loja preferida': 'LojaX', -'canal preferido': 'Loja Física' -}); -await -dito -. -identifyUser -( -); -``` - -Isso resultará no envio do seguinte payload do usuário ao chamar `identifyUser()`: - -```javascript -{ - name: 'João da Silva', - email: 'joao@example.com', - location: 'Rio de Janeiro', - customData: { - 'loja preferida': 'LojaX', - 'canal preferido': 'Loja Física' - } -} -``` - -A nossa SDK é uma instância única, o que significa que, mesmo que ela seja inicializada em vários -arquivos ou mais de uma vez, ela sempre referenciará as mesmas informações previamente armazenadas. -Isso nos proporciona a flexibilidade de chamar o método `identify()` a qualquer momento para -adicionar ou atualizar os detalhes do usuário, e somente quando necessário, enviá-los através do -método `identifyUser()`. - -#### arquivoZ.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Registra um evento na Dito -dito.trackEvent -( -eventName: 'comprou produto', -revenue: 99.90, -customData: { -'produto': 'produtoX', -'sku_produto': '99999999', -'metodo_pagamento': 'Visa', -}, -); +await dito.trackEvent(eventName: 'login'); ``` ### Uso da SDK com push notification: @@ -364,22 +200,16 @@ dentro dos App's Android e iOS. ```dart import 'package:dito_sdk/dito_sdk.dart'; -// Método para registrar um serviço que irá receber os push quando o app estiver totalmente fechado +// Método para registrar um serviço que irá receber as mensagens quando o app estiver totalmente fechado ou em segundo plano @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); - - dito.notificationService().showLocalNotification(CustomNotification( - id: message.hashCode, - title: notification.details.title || "O nome do aplicativo", - body: notification.details.message, - payload: notification)); + DitoSDK dito = DitoSDK(); + dito.onBackgroundMessageHandler(message, + apiKey: Constants.ditoApiKey, secretKey: Constants.ditoSecretKey); } void main() async { WidgetsFlutterBinding.ensureInitialized(); - - await Firebase.initializeApp(); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); DitoSDK dito = DitoSDK(); @@ -388,5 +218,5 @@ void main() async { } ``` -> Lembre-se de substituir 'sua_api_key', 'sua_secret_key' e 'id_do_usuario' pelos valores corretos +> Lembre-se de substituir 'sua_api_key', 'sua_secret_key' pelos valores corretos > em seu ambiente. diff --git a/package/buf.gen.yaml b/package/buf.gen.yaml new file mode 100644 index 0000000..f2cdc48 --- /dev/null +++ b/package/buf.gen.yaml @@ -0,0 +1,8 @@ +version: v2 +managed: + enabled: true +plugins: + - remote: buf.build/protocolbuffers/dart:v21.1.2 + out: lib/proto +inputs: + - directory: proto diff --git a/package/buf.lock b/package/buf.lock new file mode 100644 index 0000000..4f98143 --- /dev/null +++ b/package/buf.lock @@ -0,0 +1,2 @@ +# Generated by buf. DO NOT EDIT. +version: v2 diff --git a/package/buf.yaml b/package/buf.yaml new file mode 100644 index 0000000..61ae974 --- /dev/null +++ b/package/buf.yaml @@ -0,0 +1,11 @@ +# For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml +version: v2 +modules: + - path: proto + name: buf.build/dito/sdk-service +lint: + use: + - STANDARD +breaking: + use: + - FILE diff --git a/package/lib/api/dito_api_interface.dart b/package/lib/api/dito_api_interface.dart new file mode 100644 index 0000000..cbccd9e --- /dev/null +++ b/package/lib/api/dito_api_interface.dart @@ -0,0 +1,381 @@ +import 'dart:io'; + +import 'package:http/http.dart' as http; +import 'package:uuid/uuid.dart'; + +import '../event/event_entity.dart'; +import '../event/navigation_entity.dart'; +import '../notification/notification_entity.dart'; +import '../proto/google/protobuf/timestamp.pb.dart'; +import '../proto/sdkapi/v1/api.pb.dart' as rpcAPI; +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(); + + Timestamp _parseToTimestamp(String? time) { + return time != null + ? Timestamp.fromDateTime(DateTime.parse(time)) + : Timestamp.fromDateTime(DateTime.now()); + } + + 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 = _parseToTimestamp(time); + + 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 = _parseToTimestamp(time); + + 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 = _parseToTimestamp(time); + + 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 = _parseToTimestamp(time); + + 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 = _parseToTimestamp(time); + + 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 = _parseToTimestamp(time); + + 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 = _parseToTimestamp(time); + + 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 = _parseToTimestamp(time); + + 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 = _parseToTimestamp(time); + + 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 response.statusCode; + } + + throw ('Unknown error occurred.'); + } +} diff --git a/package/lib/constants.dart b/package/lib/constants.dart index e964ebc..8eb6a72 100644 --- a/package/lib/constants.dart +++ b/package/lib/constants.dart @@ -4,8 +4,6 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart'; class Constants { - String platform = Platform.isIOS ? 'iPhone' : 'Android'; - Future getUserAgent() async { final deviceInfo = DeviceInfoPlugin(); final PackageInfo packageInfo = await PackageInfo.fromPlatform(); @@ -26,44 +24,3 @@ class Constants { return '$appName/$version ($system; $model)'; } } - -enum Endpoint { - identify, - registryMobileTokens, - removeMobileTokens, - events, - openNotification; - - replace(String id) { - String? value; - - switch (toString()) { - case "Endpoint.registryMobileTokens": - value = - "notification.plataformasocial.com.br/users/{}/mobile-tokens/" - .replaceFirst("{}", id); - break; - case "Endpoint.removeMobileTokens": - value = - "notification.plataformasocial.com.br/users/{}/mobile-tokens/disable/" - .replaceFirst("{}", id); - break; - case "Endpoint.events": - value = - "events.plataformasocial.com.br/users/{}" - .replaceFirst("{}", id); - break; - case "Endpoint.openNotification": - value = - "notification.plataformasocial.com.br/notifications/{}/open" - .replaceFirst("{}", id); - break; - default: - value = "login.plataformasocial.com.br/users/portal/{}/signup" - .replaceFirst("{}", id); - break; - } - - return value; - } -} diff --git a/package/lib/data/database.dart b/package/lib/data/database.dart new file mode 100644 index 0000000..3e1f959 --- /dev/null +++ b/package/lib/data/database.dart @@ -0,0 +1,144 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; + +import '../utils/logger.dart'; + +/// EventDatabaseService is a singleton class that provides methods to interact with a SQLite database +/// for storing and managing events. +class LocalDatabase { + static const String _dbName = 'dito-offline.db'; + static Database? _database; + final tables = { + "notification": "notification", + "events": "events", + "user": "user" + }; + + static final LocalDatabase _instance = LocalDatabase._internal(); + + /// Factory constructor to return the singleton instance of EventDatabaseService. + factory LocalDatabase() { + return _instance; + } + + /// Private named constructor for internal initialization of singleton instance. + LocalDatabase._internal(); + + /// Getter for the database instance. + /// Initializes the database if it is not already initialized. + /// Returns a Future that completes with the database instance. + Future get database async { + if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { + databaseFactoryOrNull = null; + sqfliteFfiInit(); + databaseFactory = databaseFactoryFfi; + } + + if (_database != null) return _database!; + + _database = await _initDatabase(); + return _database!; + } + + /// Private method to initialize the database. + /// Sets up the path and opens the database, creating it if it does not exist. + /// Returns a Future that completes with the database instance. + Future _initDatabase() async { + try { + final databasePath = await getDatabasesPath(); + final String path = '$databasePath/$_dbName'; + + return await openDatabase( + path, + version: 2, + onCreate: _createTable, + ); + } catch (e) { + loggerError('Error initializing database: $e'); + rethrow; + } + } + + /// Callback method to create the events table when the database is first created. + /// + /// [db] - The database instance. + /// [version] - The version of the database. + FutureOr _createTable(Database db, int version) async { + try { + await db.execute(''' + CREATE TABLE events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + event TEXT, + uuid TEXT, + createdAt TEXT + ); + '''); + await db.execute(''' + CREATE TABLE user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + user TEXT, + uuid TEXT, + createdAt TEXT + ); + '''); + } catch (e) { + loggerError('Error creating table: $e'); + + rethrow; + } + } + + Future insert(String table, Map values) async { + final db = await database; + return db.insert(table, values); + } + + Future delete( + String table, String where, List whereArgs) async { + try { + final db = await database; + return await db.delete( + table, + where: where, + whereArgs: whereArgs, + ); + } catch (e) { + loggerError('Error deleting event: $e'); + + rethrow; + } + } + + /// Method to retrieve all events from the events table. + /// Returns a Future that completes with a list of Map objects. + Future>> fetchAll(String tableName) async { + final db = await database; + return db.query(tableName); + } + + /// Method to clear all events from the events table. + /// Returns a Future that completes with the number of rows deleted. + Future clearDatabase(String tableName) async { + try { + final db = await database; + await db.delete(tableName); + } catch (e) { + loggerError('Error clearing event: $e'); + + rethrow; + } + } + + /// Method to close the database. + /// Should be called when the database is no longer needed. + Future closeDatabase() async { + if (_database != null) { + await _database!.close(); + _database = null; + } + } +} diff --git a/package/lib/database.dart b/package/lib/database.dart deleted file mode 100644 index a7c9de4..0000000 --- a/package/lib/database.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:io'; - -import 'package:sqflite_common_ffi/sqflite_ffi.dart'; - -import 'entity/event.dart'; - -class LocalDatabase { - static final LocalDatabase instance = LocalDatabase._privateConstructor(); - static Database? _database; - - LocalDatabase._privateConstructor(); - - Future get database async { - if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { - databaseFactoryOrNull = null; - sqfliteFfiInit(); - databaseFactory = databaseFactoryFfi; - } - - if (_database != null) return _database!; - - _database = await _initDatabase(); - return _database!; - } - - Future _initDatabase() async { - final databasePath = await getDatabasesPath(); - final String path = '$databasePath/ditoSDK.db'; - - return await openDatabase( - path, - version: 1, - onCreate: _createTable, - ); - } - - Future _createTable(Database db, int version) async { - await db.execute(''' - CREATE TABLE events ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - eventName TEXT, - eventMoment TEXT, - revenue REAL, - customData TEXT - ) - '''); - } - - Future createEvent(Event event) async { - final db = await database; - await db.insert('events', event.toMap()); - } - - Future deleteEvent(Event event) async { - final db = await database; - return await db.delete( - 'events', - where: 'eventName = ? AND eventMoment = ?', - whereArgs: [event.eventName, event.eventMoment], - ); - } - - Future> getEvents() async { - final db = await database; - final List> maps = await db.query('events'); - - return List.generate(maps.length, (index) { - return Event.fromMap(maps[index]); - }); - } - - Future deleteEvents() async { - final db = await database; - return await db.delete('events'); - } -} diff --git a/package/lib/dito_sdk.dart b/package/lib/dito_sdk.dart index edf7eb2..38c4f1d 100644 --- a/package/lib/dito_sdk.dart +++ b/package/lib/dito_sdk.dart @@ -1,27 +1,23 @@ library dito_sdk; -import 'dart:convert'; +import 'dart:io'; -import 'package:dito_sdk/entity/domain.dart'; -import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:http/http.dart' as http; +import 'package:package_info_plus/package_info_plus.dart'; -import 'constants.dart'; -import 'database.dart'; -import 'entity/event.dart'; -import 'entity/user.dart'; -import 'services/notification_service.dart'; -import 'utils/http.dart'; -import 'utils/sha1.dart'; +import 'api/dito_api_interface.dart'; +import 'event/event_interface.dart'; +import 'notification/notification_interface.dart'; +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 { - String? _apiKey; - String? _secretKey; - late Map _assign; - late NotificationService _notificationService; - User _user = User(); - Constants constants = Constants(); + 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(); @@ -31,251 +27,45 @@ class DitoSDK { DitoSDK._internal(); - NotificationService notificationService() { - return _notificationService; - } + /// This get method provides an interface for communication with a User entity. + /// Returns an instance of UserInterface class. + UserInterface get user => _userInterface; - User get user { - return _user; - } + /// This get method provides an interface for communication with a Notifications. + /// Returns an instance of NotificationInterface class. + NotificationInterface get notification => _notificationInterface; - void initialize({required String apiKey, required String secretKey}) async { - _apiKey = apiKey; - _secretKey = secretKey; - _notificationService = NotificationService(_instance); - _assign = { - 'platform_api_key': apiKey, - 'sha1_signature': convertToSHA1(_secretKey!), - }; - } + /// This get method provides an interface for communication with a Event. + /// Returns an instance of EventInterface class. + EventInterface get event => _eventInterface; + /// This method initializes the SDK with the provided API key and secret key. + /// It also initializes the NotificationService and assigns API key and SHA1 signature. + /// + /// [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 = !Platform.environment.containsKey('FLUTTER_TEST') ? await PackageInfo.fromPlatform(): null; + _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 ?? '1'; + _appInfo.version = packageInfo?.version ?? '1.0.0'; + _appInfo.id = packageInfo?.packageName ?? ''; + } + + /// This method initializes the push notification service using Firebase. Future initializePushNotificationService() async { - await Firebase.initializeApp(); - await _notificationService.initialize(); - - RemoteMessage? initialMessage = - await FirebaseMessaging.instance.getInitialMessage(); - - if (initialMessage != null) { - _notificationService.handleMessage(initialMessage); - } - - FirebaseMessaging.onMessageOpenedApp - .listen(_notificationService.handleMessage); - } - - 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 _verifyPendingEvents() async { - final database = LocalDatabase.instance; - final events = await database.getEvents(); - - if (events.isNotEmpty) { - for (final event in events) { - await _postEvent(event); - } - database.deleteEvents(); - } - } - - @Deprecated('migration') - Future setUserId(String userId) async { - _setUserId(userId); - } - - Future _setUserId(String userId) async { - if (_user.isValid) { - _verifyPendingEvents(); - } - } - - void identify({ - required String userID, - String? cpf, - String? name, - String? email, - String? gender, - String? birthday, - String? location, - Map? customData, - }) { - _user.userID = userID; - - if (cpf != null) { - _user.cpf = cpf; - } - - if (name != null) { - _user.name = name; - } - - if (email != null) { - _user.email = email; - } - - if (gender != null) { - _user.gender = gender; - } - - if (birthday != null) { - _user.birthday = birthday; - } - - if (location != null) { - _user.location = location; - } - - if (customData != null) { - _user.customData = customData; - } - - _setUserId(userID); - } - - Future setUser(User user) async { - _user = user; - - if (_user.isValid) { - await _setUserId(_user.id!); - } else { - throw Exception( - 'User registration is required. Please call the identify() method first.'); - } + await _notificationInterface.initialize(); } - Future identifyUser() async { - _checkConfiguration(); - - if (_user.isNotValid) { - throw Exception( - 'User registration is required. Please call the identify() method first.'); - } - - final queryParameters = { - 'user_data': jsonEncode(_user.toJson()), - }; - - queryParameters.addAll(_assign); - final url = Domain(Endpoint.identify.replace(_user.id!)).spited; - final uri = Uri.https(url[0], url[1], queryParameters); - - return await Api().post( - url: uri, - ); - } - - Future _postEvent(Event event) async { - _checkConfiguration(); - - final body = { - 'id_type': 'id', - 'network_name': 'pt', - 'event': jsonEncode(event.toJson()) - }; - - final url = Domain(Endpoint.events.replace(_user.id!)).spited; - final uri = Uri.https(url[0], url[1], _assign); - - body.addAll(_assign); - return await Api().post(url: uri, body: body); - } - - Future trackEvent({ - required String eventName, - double? revenue, - Map? customData, - }) async { - DateTime localDateTime = DateTime.now(); - DateTime utcDateTime = localDateTime.toUtc(); - String eventMoment = utcDateTime.toIso8601String(); - - final event = Event( - eventName: eventName, - eventMoment: eventMoment, - customData: customData, - revenue: revenue); - - if (_user.isNotValid) { - final database = LocalDatabase.instance; - await database.createEvent(event); - return http.Response("", 200); - } - - return await _postEvent(event); - } - - Future registryMobileToken({required String token}) async { - _checkConfiguration(); - - if (_user.isNotValid) { - throw Exception( - 'User registration is required. Please call the identify() method first.'); - } - - final queryParameters = { - 'id_type': 'id', - 'token': token, - 'platform': constants.platform, - }; - - queryParameters.addAll(_assign); - final url = Domain(Endpoint.registryMobileTokens.replace(_user.id!)).spited; - final uri = Uri.https(url[0], url[1], queryParameters); - - return await Api().post( - url: uri, - ); - } - - Future removeMobileToken({required String token}) async { - _checkConfiguration(); - - if (_user.isNotValid) { - throw Exception( - 'User registration is required. Please call the identify() method first.'); - } - - final queryParameters = { - 'id_type': 'id', - 'token': token, - 'platform': constants.platform, - }; - - queryParameters.addAll(_assign); - final url = Domain(Endpoint.removeMobileTokens.replace(_user.id!)).spited; - final uri = Uri.https(url[0], url[1], queryParameters); - - return await Api().post( - url: uri, - ); - } - - Future openNotification( - {required String notificationId, - required String identifier, - required String reference}) async { - _checkConfiguration(); - - final queryParameters = { - 'channel_type': 'mobile', - }; - - final body = { - 'identifier': identifier, - 'reference': reference, - }; - - queryParameters.addAll(_assign); - - final url = - Domain(Endpoint.openNotification.replace(notificationId)).spited; - final uri = Uri.https(url[0], url[1], queryParameters); - - return await Api().post(url: uri, body: body); + /// This method is a handler for manage messages in the background. + /// It initializes Firebase and Dito, then push the message. + void onBackgroundPushNotificationHandler( + {required RemoteMessage message}) async { + await initializePushNotificationService(); + await _notificationInterface.onMessage(message); } } diff --git a/package/lib/entity/custom_notification.dart b/package/lib/entity/custom_notification.dart deleted file mode 100644 index 5543655..0000000 --- a/package/lib/entity/custom_notification.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:firebase_messaging/firebase_messaging.dart'; - -import 'data_payload.dart'; - -class CustomNotification { - final int id; - final String title; - final String body; - final DataPayload? payload; - final RemoteMessage? remoteMessage; - - CustomNotification({ - required this.id, - required this.title, - required this.body, - this.payload, - this.remoteMessage, - }); -} diff --git a/package/lib/entity/data_payload.dart b/package/lib/entity/data_payload.dart deleted file mode 100644 index f2cf865..0000000 --- a/package/lib/entity/data_payload.dart +++ /dev/null @@ -1,46 +0,0 @@ -class Details { - final String? link; - final String message; - - Details(this.link, this.message); - - factory Details.fromJson(dynamic json) { - assert(json is Map); - return Details(json["link"], json["message"]); - } - - Map toJson() => { - 'link': link, - 'message': message, - }; -} - -class DataPayload { - final String reference; - final String identifier; - final String notification; - final String notification_log_id; - final Details details; - - DataPayload(this.reference, this.identifier, this.notification, - this.notification_log_id, this.details); - - factory DataPayload.fromJson(dynamic json) { - assert(json is Map); - - return DataPayload( - json["reference"], - json["identifier"], - json["notification"], - json["notification_log_id"], - Details.fromJson(json["details"])); - } - - Map toJson() => { - 'reference': reference, - 'identifier': identifier, - 'notification': notification, - 'notification_log_id': notification_log_id, - 'details': details.toJson(), - }; -} diff --git a/package/lib/entity/domain.dart b/package/lib/entity/domain.dart deleted file mode 100644 index 38e5d61..0000000 --- a/package/lib/entity/domain.dart +++ /dev/null @@ -1,14 +0,0 @@ -class Domain { - final String url; - - Domain(this.url); - - String get uri { - return url; - } - - List get spited { - int idx = url.indexOf("/"); - return [url.substring(0, idx).trim(), url.substring(idx + 1).trim()]; - } -} diff --git a/package/lib/entity/event.dart b/package/lib/entity/event.dart deleted file mode 100644 index afece0e..0000000 --- a/package/lib/entity/event.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:convert'; - -class Event { - final String eventName; - final String eventMoment; - final double? revenue; - final Map? customData; - - Event({ - required this.eventName, - required this.eventMoment, - this.revenue, - this.customData, - }); - - factory Event.fromMap(Map map) { - return Event( - eventName: map['eventName'], - eventMoment: map['eventMoment'], - revenue: map['revenue'], - customData: map['customData'] != null - ? (json.decode(map['customData']) as Map) - .map((key, value) => MapEntry(key, value as String)) - : null, - ); - } - - Map toMap() { - return { - 'eventName': eventName, - 'eventMoment': eventMoment, - 'revenue': revenue, - 'customData': customData != null ? json.encode(customData) : null, - }; - } - - Map toJson() => { - 'action': eventName, - 'revenue': revenue, - 'data': customData, - 'created_at': eventMoment - }; -} diff --git a/package/lib/event/event_dao.dart b/package/lib/event/event_dao.dart new file mode 100644 index 0000000..6120613 --- /dev/null +++ b/package/lib/event/event_dao.dart @@ -0,0 +1,122 @@ +import 'dart:convert'; + +import 'package:dito_sdk/notification/notification_entity.dart'; + +import '../data/database.dart'; +import '../utils/logger.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 EventDAO { + static final LocalDatabase _database = LocalDatabase(); + static final EventDAO _instance = EventDAO._internal(); + + get _table => _database.tables["events"]; + + /// Factory constructor to return the singleton instance of EventDatabaseService. + factory EventDAO() { + return _instance; + } + + /// Private named constructor for internal initialization of singleton instance. + 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(EventsNames eventType, String uuid, + {EventEntity? event, + NavigationEntity? navigation, + NotificationEntity? notification}) async { + try { + 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) { + loggerError('Error inserting event: $e'); + rethrow; + } + } + + /// Method to delete an event from the events table. + /// + /// [event] - The EventEntity object to be deleted. + /// Returns a Future that completes with the number of rows deleted. + Future delete(EventEntity event) async { + try { + return await _database.delete( + _table, + 'name = ? AND createdAt = ?', + [event], + ) > + 0; + } catch (e) { + loggerError('Error deleting event: $e'); + + rethrow; + } + } + + /// Method to retrieve all events from the events table. + /// Returns a Future that completes with a list of Map objects. + Future fetchAll() async { + try { + return await _database.fetchAll(_table); + } catch (e) { + loggerError('Error retrieving events: $e'); + + rethrow; + } + } + + /// Method to clear all events from the events table. + /// Returns a Future that completes with the number of rows deleted. + Future clearDatabase() async { + try { + return _database.clearDatabase(_table); + } catch (e) { + loggerError('Error clearing database: $e'); + rethrow; + } + } + + /// Method to close the database. + /// Should be called when the database is no longer needed. + Future closeDatabase() => _database.closeDatabase(); +} diff --git a/package/lib/event/event_entity.dart b/package/lib/event/event_entity.dart new file mode 100644 index 0000000..b9a0fcb --- /dev/null +++ b/package/lib/event/event_entity.dart @@ -0,0 +1,42 @@ +import 'dart:convert'; + +class EventEntity { + String action; + String? createdAt; + double? revenue; + String? currency; + Map? customData; + + EventEntity({ + required this.action, + this.revenue, + this.createdAt, + this.currency, + this.customData, + }); + + factory EventEntity.fromMap(Map map) { + return EventEntity( + action: map['action'], + revenue: map['revenue'], + createdAt: map['createdAt'], + currency: map['currency'], + customData: + map['customData'] != null ? jsonDecode(map['customData']) : null, + ); + } + + Map toJson() { + final json = { + 'action': action, + 'revenue': revenue, + 'currency': currency, + 'data': customData, + 'created_at': createdAt + }; + + json.removeWhere((key, value) => value == null); + + return json; + } +} diff --git a/package/lib/event/event_interface.dart b/package/lib/event/event_interface.dart new file mode 100644 index 0000000..2a885d1 --- /dev/null +++ b/package/lib/event/event_interface.dart @@ -0,0 +1,86 @@ +import '../utils/logger.dart'; +import 'event_entity.dart'; +import 'event_repository.dart'; +import 'navigation_entity.dart'; + +/// `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 a user event. + /// + /// [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(); + + // 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); + + // Track the event using the repository and return the result. + return _repository.track(event); + } catch (e) { + loggerError('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); + + // Track the navigation event using the repository and return the result. + return _repository.navigate(navigation); + } catch (e) { + loggerError( + 'Error tracking navigation event: $e'); // Log the error in debug mode. + + 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 new file mode 100644 index 0000000..facdf33 --- /dev/null +++ b/package/lib/event/event_repository.dart @@ -0,0 +1,125 @@ +import 'dart:async'; +import 'dart:convert'; + +import '../api/dito_api_interface.dart'; +import '../notification/notification_entity.dart'; +import '../proto/sdkapi/v1/api.pb.dart'; +import '../user/user_repository.dart'; +import '../utils/logger.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 ApiInterface _api = ApiInterface(); + final UserRepository _userRepository = UserRepository(); + 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. + /// + /// [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 track(EventEntity event) async { + final activity = ApiActivities().trackEvent(event); + + if (_userRepository.data.isNotValid) { + return await _database.create(EventsNames.track, activity.id, + event: event); + } + + final result = await _api.createRequest([activity]).call(); + + print(result); + + if (result >= 400 && result < 500) { + await _database.create(EventsNames.track, activity.id, event: event); + return false; + } + + return true; + } + + /// 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 (_userRepository.data.isNotValid) { + return await _database.create(EventsNames.navigate, activity.id, + navigation: navigation); + } + + final result = await _api.createRequest([activity]).call(); + + if (result >= 400 && result < 500) { + await _database.create(EventsNames.navigate, activity.id, + navigation: navigation); + return false; + } + + return true; + } + + /// Verifies and processes any pending events. + /// + /// Throws an exception if the user is not valid. + Future verifyPendingEvents() async { + try { + 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; + } + } + + if (activities.isNotEmpty) { + await _api.createRequest(activities).call(); + } + + return await _database.clearDatabase(); + } catch (e) { + loggerError('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..df2c007 --- /dev/null +++ b/package/lib/event/navigation_entity.dart @@ -0,0 +1,30 @@ +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 new file mode 100644 index 0000000..8b71cc5 --- /dev/null +++ b/package/lib/notification/notification_controller.dart @@ -0,0 +1,114 @@ +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'; + +class NotificationController { + late FlutterLocalNotificationsPlugin localNotificationsPlugin; + Function(RemoteMessage)? _selectNotification; + + /// Android-specific notification details. + AndroidNotificationDetails androidNotificationDetails = + const AndroidNotificationDetails( + 'dito_notifications', // Notification channel ID. + 'Notifications sent by Dito', // Notification channel name. + channelDescription: + 'Notifications sent by Dito', // Notification channel description. + importance: Importance.max, // Maximum importance level. + priority: Priority.max, // Maximum priority level. + enableVibration: true, // Enable vibration. + ); + + /// iOS-specific notification details. + DarwinNotificationDetails darwinNotificationDetails = + const DarwinNotificationDetails( + presentAlert: true, // Display alert. + presentBadge: true, // Display badge. + presentSound: true, // Play sound. + presentBanner: true, // Display banner. + ); + + NotificationController._internal(); + + static final NotificationController _instance = + NotificationController._internal(); + + factory NotificationController() { + return _instance; + } + + /// Initializes the local notifications plugin. + /// + /// [onSelectNotification] - Callback function to handle notification selection. + Future initialize(Function(RemoteMessage) selectNotification) async { + _selectNotification = selectNotification; + localNotificationsPlugin = FlutterLocalNotificationsPlugin(); + + const android = AndroidInitializationSettings('@mipmap/ic_launcher'); + const ios = DarwinInitializationSettings(); + + const InitializationSettings initializationSettings = + InitializationSettings(android: android, iOS: ios); + + await localNotificationsPlugin.initialize(initializationSettings, + onDidReceiveNotificationResponse: onTapNotification); + + await _setupAndroidChannel(); + } + + /// Sets up the Android notification channel. + Future _setupAndroidChannel() async { + const AndroidNotificationChannel channel = AndroidNotificationChannel( + 'dito_notifications', // Channel ID. + 'Notifications sent by Dito', // Channel name. + importance: Importance.max, // Maximum importance level. + ); + + await localNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(channel); + } + + /// Displays a notification. + /// + /// [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( + displayInfo.id, // Notification ID. + displayInfo.title, // Notification title. + displayInfo.body, // Notification body. + NotificationDetails( + android: androidNotificationDetails, iOS: darwinNotificationDetails), + payload: + jsonEncode(notification.details?.toJson()), // Notification payload. + ); + } + + /// Handles notification selection by the user. + /// + /// [response] - NotificationResponse object containing response details. + Future onTapNotification(NotificationResponse? response) async { + final payload = response?.payload; + + if (payload != null && payload.isNotEmpty) { + if (_selectNotification != null) + _selectNotification!(jsonDecode(payload) as RemoteMessage); + } + } +} diff --git a/package/lib/notification/notification_entity.dart b/package/lib/notification/notification_entity.dart new file mode 100644 index 0000000..04ebb6d --- /dev/null +++ b/package/lib/notification/notification_entity.dart @@ -0,0 +1,117 @@ +import 'dart:io'; + +class DetailsEntity { + final String? link; + final String? title; + final String message; + String? image; + + DetailsEntity(this.title, this.message, this.link, this.image); + + factory DetailsEntity.fromJson(dynamic json) { + assert(json is Map); + return DetailsEntity( + json["title"], json["message"], json["link"], json["image"]); + } + + Map toJson() => + {'link': link, 'message': message, 'title': title, 'image': image}; +} + +class NotificationEntity { + final String notification; + final String? notificationLogId; + final String? contactId; + final String? reference; + final String? userId; + final String? name; + final DetailsEntity? details; + final String? createdAt; + + NotificationEntity({ + required this.notification, + this.notificationLogId, + this.contactId, + this.reference, + this.userId, + this.name, + this.details, + this.createdAt, + }); + + factory NotificationEntity.fromMap(dynamic json) { + final String? image; + assert(json is Map); + + if (Platform.isAndroid) { + image = json["notification"]?["android"]?["imageUrl"]; + } else { + image = json["notification"]?["apple"]?["imageUrl"]; + } + + final String title = + json["notification"]?["title"] ?? json["data"]["title"]; + final String message = + json["notification"]?["body"] ?? json["data"]["message"]; + final String link = json["data"]["link"]; + + final DetailsEntity details = DetailsEntity(title, message, link, image); + + 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 NotificationEntity.fromPayload(dynamic json) { + assert(json is Map); + + 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() => { + 'notification': notification, + 'notificationLogId': notificationLogId, + 'contactId': contactId, + 'reference': reference, + 'userId': userId, + 'name': name, + 'createdAt': createdAt, + 'details': details, + }; +} + +class NotificationDisplayEntity { + int id; + String title; + String body; + String? notificationId; + String? image; + Map? data; + + NotificationDisplayEntity({ + required this.id, + required this.title, + required this.body, + this.notificationId, + this.image, + this.data, + }); +} diff --git a/package/lib/notification/notification_events.dart b/package/lib/notification/notification_events.dart new file mode 100644 index 0000000..d55fd9f --- /dev/null +++ b/package/lib/notification/notification_events.dart @@ -0,0 +1,22 @@ +import 'package:event_bus/event_bus.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; + +class MessageClickedEvent { + RemoteMessage message; + + MessageClickedEvent(this.message); +} + +class NotificationEvents { + EventBus eventBus = EventBus(); + + static final NotificationEvents _instance = NotificationEvents._internal(); + + factory NotificationEvents() { + return _instance; + } + + EventBus get stream => eventBus; + + NotificationEvents._internal(); +} diff --git a/package/lib/notification/notification_interface.dart b/package/lib/notification/notification_interface.dart new file mode 100644 index 0000000..52116fb --- /dev/null +++ b/package/lib/notification/notification_interface.dart @@ -0,0 +1,194 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; + +import '../user/user_interface.dart'; +import '../utils/logger.dart'; +import 'notification_controller.dart'; +import 'notification_entity.dart'; +import 'notification_events.dart'; +import 'notification_repository.dart'; + +/// `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(RemoteMessage message) onMessageClick; + final NotificationRepository _repository = NotificationRepository(); + final NotificationController _controller = NotificationController(); + final NotificationEvents _notificationEvents = NotificationEvents(); + final UserInterface _userInterface = UserInterface(); + bool initialized = false; + 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 { + if (Firebase.apps.isEmpty) { + throw 'Firebase not initialized'; + } + + if (initialized) return; + await FirebaseMessaging.instance.setAutoInitEnabled(true); + + // For iOS, set notification presentation options. + if (Platform.isIOS) { + await FirebaseMessaging.instance + .setForegroundNotificationPresentationOptions( + badge: true, sound: true, alert: true); + } + + FirebaseMessaging.onMessage.listen(onMessage); + _handleToken(); + await _controller.initialize(onSelectNotification); + + _listenStream(); + + initialized = true; + } + + void _handleToken() async { + _userInterface.data.token = await token; + + FirebaseMessaging.instance.onTokenRefresh.listen((token) { + _userInterface.data.token = token; + _userInterface.token.pingToken(token); + }).onError((err) { + loggerError(err); + }); + } + + /// Disposes of notification streams, ensuring that all resources are released. + void dispose() { + _repository.didReceiveLocalNotificationStream.close(); + _repository.selectNotificationStream.close(); + } + + /// Listens for events in the notification streams and triggers appropriate actions. + _listenStream() { + _repository.didReceiveLocalNotificationStream.stream + .listen((NotificationEntity receivedNotification) async { + _controller.showNotification(receivedNotification); + + await _repository.received(NotificationEntity( + notification: receivedNotification.notification, + notificationLogId: receivedNotification.notificationLogId, + contactId: receivedNotification.contactId, + name: receivedNotification.name)); + }); + + _repository.selectNotificationStream.stream + .listen((RemoteMessage message) async { + _notificationEvents.stream.fire(MessageClickedEvent(message)); + + final data = message.data; + final notification = NotificationEntity( + notification: data["notification"], + notificationLogId: data["notificationLogId"]!, + contactId: data["contactId"], + name: data["name"]); + + await _repository.click(notification); + onMessageClick(message); + }); + } + + /// Handles incoming messages from Firebase and triggers appropriate actions based on the content. + /// + /// [message] - The incoming [RemoteMessage] from Firebase. + Future onMessage(RemoteMessage message) async { + if (message.data.isEmpty) { + loggerError("Data is not defined: $message"); + } + + final notification = NotificationEntity.fromMap(message.toMap()); + + _repository.received(notification); + + final messagingAllowed = await _checkPermissions(); + + if (messagingAllowed && notification.details?.message != null) { + _repository.didReceiveLocalNotificationStream.add(notification); + } + } + + /// Marks a notification as received in the repository. + /// + /// [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 _repository.received(NotificationEntity( + notification: notification, + notificationLogId: notificationLogId, + contactId: contactId, + name: name)); + } catch (e) { + loggerError( + 'Error tracking click event: $e'); // Log the error in debug mode. + + return false; // Return false if there was an error. + } + } + + /// Marks a notification as clicked in the repository. + /// + /// [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) { + loggerError( + 'Error tracking click event: $e'); // Log the error in debug mode. + + return false; // Return false if there was an error. + } + } + + /// Checks if the user has granted permissions for receiving notifications. + /// + /// Returns `true` if notifications are authorized, `false` otherwise. + Future _checkPermissions() async { + final settings = await FirebaseMessaging.instance.requestPermission(); + return settings.authorizationStatus == AuthorizationStatus.authorized; + } + + /// Handles notification selection events and triggers appropriate actions. + /// + /// [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 new file mode 100644 index 0000000..ccec4b6 --- /dev/null +++ b/package/lib/notification/notification_repository.dart @@ -0,0 +1,68 @@ +import 'dart:async'; + +import 'package:firebase_messaging/firebase_messaging.dart'; + +import '../api/dito_api_interface.dart'; +import '../event/event_dao.dart'; +import '../user/user_repository.dart'; +import 'notification_entity.dart'; + +class NotificationRepository { + final ApiInterface _api = ApiInterface(); + final UserRepository _userRepository = UserRepository(); + 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(); + + /// This method send a notify click on push event to Dito + /// + /// [notification] - Notification object. + Future click(NotificationEntity notification) async { + final activity = ApiActivities().notificationClick(notification); + + if (_userRepository.data.isNotValid) { + return await _database.create(EventsNames.click, activity.id, + notification: notification); + } + + final result = await _api.createRequest([activity]).call(); + + if (result >= 400 && result < 500) { + await _database.create(EventsNames.click, activity.id, + notification: notification); + + return false; + } + + return true; + } + + /// This method send a notify received push event to Dito + /// + /// [notification] - Notification object. + Future received(NotificationEntity notification) async { + final activity = ApiActivities().notificationReceived(notification); + + if (_userRepository.data.isNotValid) { + return await _database.create(EventsNames.received, activity.id, + notification: notification); + } + + final result = await _api.createRequest([activity]).call(); + + if (result >= 400 && result < 500) { + await _database.create(EventsNames.received, activity.id, + notification: notification); + + return false; + } + + return true; + } +} diff --git a/package/lib/services/notification_service.dart b/package/lib/services/notification_service.dart deleted file mode 100644 index 14c1457..0000000 --- a/package/lib/services/notification_service.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -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 '../dito_sdk.dart'; -import '../entity/custom_notification.dart'; -import '../entity/data_payload.dart'; - -class NotificationService { - bool _messagingAllowed = false; - late String _appName; - late DitoSDK _dito; - late FlutterLocalNotificationsPlugin localNotificationsPlugin; - final StreamController didReceiveLocalNotificationStream = - StreamController.broadcast(); - final StreamController selectNotificationStream = - StreamController.broadcast(); - - AndroidNotificationDetails androidDetails = const AndroidNotificationDetails( - 'dito_notifications', - 'Notifications sended by Dito', - channelDescription: 'Notifications sended by Dito', - importance: Importance.max, - priority: Priority.max, - enableVibration: true, - ); - - DarwinNotificationDetails iosDetails = const DarwinNotificationDetails( - presentAlert: true, - presentBadge: true, - presentSound: true, - presentBanner: true); - - NotificationService(DitoSDK dito) { - _dito = dito; - } - - Future initialize() async { - if (Platform.isAndroid) { - await FirebaseMessaging.instance.setAutoInitEnabled(true); - } - - localNotificationsPlugin = FlutterLocalNotificationsPlugin(); - _setupNotifications(); - - await FirebaseMessaging.instance - .setForegroundNotificationPresentationOptions( - badge: true, sound: true, alert: true); - - PackageInfo packageInfo = await PackageInfo.fromPlatform(); - _appName = packageInfo.appName; - - await checkPermissions(); - _onMessage(); - } - - Future getDeviceFirebaseToken() async { - return FirebaseMessaging.instance.getToken(); - } - - _onMessage() { - FirebaseMessaging.onMessage.listen(handleMessage); - FirebaseMessaging.onMessageOpenedApp.listen(handleMessage); - } - - void handleMessage(RemoteMessage message) { - if (message.data["data"] == null) { - print("Data is not defined: ${message.data}"); - } - - final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); - - if (_messagingAllowed && notification.details.message.isNotEmpty) { - didReceiveLocalNotificationStream.add(CustomNotification( - id: message.hashCode, - title: _appName, - body: notification.details.message, - payload: notification)); - } - } - - addNotificationToStream(CustomNotification notification) { - didReceiveLocalNotificationStream.add(notification); - } - - Future checkPermissions() async { - var settings = await FirebaseMessaging.instance.getNotificationSettings(); - - if (settings.authorizationStatus != AuthorizationStatus.authorized) { - await FirebaseMessaging.instance.requestPermission(); - settings = await FirebaseMessaging.instance.getNotificationSettings(); - _messagingAllowed = - (settings.authorizationStatus == AuthorizationStatus.authorized); - } else { - _messagingAllowed = true; - } - } - - _setupAndroidChannel() async { - const AndroidNotificationChannel channel = AndroidNotificationChannel( - 'dito_notifications', - 'Notifications sended by Dito', - importance: Importance.max, - ); - - await localNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(channel); - } - - _setupNotifications() async { - await _initializeNotifications(); - await _setupAndroidChannel(); - _listenStream(); - } - - _initializeNotifications() async { - const android = AndroidInitializationSettings('@mipmap/ic_launcher'); - const ios = DarwinInitializationSettings(); - - const InitializationSettings initializationSettings = - InitializationSettings(android: android, iOS: ios); - - await localNotificationsPlugin.initialize(initializationSettings, - onDidReceiveNotificationResponse: onTapNotification); - } - - void dispose() { - didReceiveLocalNotificationStream.close(); - selectNotificationStream.close(); - } - - _listenStream() { - didReceiveLocalNotificationStream.stream - .listen((CustomNotification receivedNotification) { - showLocalNotification(receivedNotification); - }); - selectNotificationStream.stream.listen((String? payload) async { - if (payload != null) { - final data = DataPayload.fromJson(jsonDecode(payload)); - - if (data.notification.isNotEmpty) { - await _dito.openNotification( - notificationId: data.notification, - identifier: data.identifier, - reference: data.reference); - } - } - }); - } - - setAndroidDetails(AndroidNotificationDetails details) { - androidDetails = details; - } - - setIosDetails(DarwinNotificationDetails details) { - iosDetails = details; - } - - showLocalNotification(CustomNotification notification) { - localNotificationsPlugin.show( - notification.id, - notification.title, - notification.body, - NotificationDetails(android: androidDetails, iOS: iosDetails), - payload: jsonEncode(notification.payload?.toJson()), - ); - } - - Future onTapNotification(NotificationResponse? response) async { - if (response?.payload != null) { - selectNotificationStream.add(response?.payload); - } - } -} 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..7f9c1c6 --- /dev/null +++ b/package/lib/user/token_repository.dart @@ -0,0 +1,162 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:dito_sdk/user/user_repository.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; + +import '../api/dito_api_interface.dart'; +import '../notification/notification_interface.dart'; +import '../proto/sdkapi/v1/api.pb.dart'; +import '../utils/logger.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(); + + Future get data async => + !Platform.environment.containsKey('FLUTTER_TEST') + ? await FirebaseMessaging.instance.getToken() + : ""; + 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); + } + + final result = await _api.createRequest([activity]).call(); + + if (result >= 400 && result < 500) { + await _userDAO.create( + UserEventsNames.registryToken, _userData, activity.id); + return false; + } + + return true; + } + + /// 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); + } + + final result = await _api.createRequest([activity]).call(); + + if (result >= 400 && result < 500) { + await _userDAO.create(UserEventsNames.pingToken, _userData, activity.id); + return false; + } + + return true; + } + + /// 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(); + + if (result >= 400 && result < 500) { + await _userDAO.create( + UserEventsNames.pingToken, _userData, activity.id); + return false; + } + + return true; + } + + /// 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) { + loggerError('Error verifying pending events on notification: $e'); + + rethrow; + } + } +} diff --git a/package/lib/user/user_dao.dart b/package/lib/user/user_dao.dart new file mode 100644 index 0000000..12526ba --- /dev/null +++ b/package/lib/user/user_dao.dart @@ -0,0 +1,93 @@ +import 'dart:convert'; + +import '../data/database.dart'; +import '../utils/logger.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 UserDAO { + static final LocalDatabase _database = LocalDatabase(); + static final UserDAO _instance = UserDAO._internal(); + get _dataTable => _database.tables["user"]; + + /// Factory constructor to return the singleton instance of EventDatabaseService. + factory UserDAO() { + return _instance; + } + + /// Private named constructor for internal initialization of singleton instance. + 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( + UserEventsNames event, UserEntity user, String uuid) async { + try { + return await _database.insert(_dataTable!, { + "name": event.name, + "user": jsonEncode(user.toJson()), + "uuid": uuid, + "createdAt": DateTime.now().toIso8601String() + }) > + 0; + } catch (e) { + loggerError('Error inserting event: $e'); + + rethrow; + } + } + + /// Method to delete an event from the notification table. + /// + /// [event] - The event name to be deleted. + /// Returns a Future that completes with the number of rows deleted. + Future delete(String userID) async { + try { + return await _database.delete( + _dataTable!, + 'userID = ?', + [userID], + ) > + 0; + } catch (e) { + loggerError('Error deleting event: $e'); + + rethrow; + } + } + + /// Method to retrieve all notification from the notification table. + /// Returns a Future that completes with a list of Map objects. + Future>> fetchAll() async { + try { + return await _database.fetchAll(_dataTable!); + } catch (e) { + loggerError('Error retrieving notification: $e'); + + rethrow; + } + } + + /// Method to clear all notification from the notification table. + /// Returns a Future that completes with the number of rows deleted. + Future clearDatabase() async { + try { + return _database.clearDatabase(_dataTable!); + } catch (e) { + loggerError('Error clearing database: $e'); + + rethrow; + } + } + + /// Method to close the database. + /// Should be called when the database is no longer needed. + Future closeDatabase() => _database.closeDatabase(); +} diff --git a/package/lib/entity/user.dart b/package/lib/user/user_entity.dart similarity index 53% rename from package/lib/entity/user.dart rename to package/lib/user/user_entity.dart index 605df6f..86b2479 100644 --- a/package/lib/entity/user.dart +++ b/package/lib/user/user_entity.dart @@ -1,44 +1,60 @@ import 'dart:convert'; -class User { +import 'address_entity.dart'; + +class UserEntity { String? userID; String? name; String? cpf; String? email; String? gender; String? birthday; - String? location; + String? phone; + AddressEntity? address; + String? token; Map? customData; - User( + UserEntity( {this.userID, this.name, this.cpf, this.email, this.gender, this.birthday, - this.location, + this.phone, + this.address, + this.token, this.customData}); String? get id => userID; + + /// User is valid when userId is not empty bool get isValid => userID != null && userID!.isNotEmpty; - bool get isNotValid => !isValid; - factory User.fromMap(Map map) { - return User( + bool get isNotValid => userID == null || userID!.isEmpty; + + // 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'], cpf: map['cpf'], 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)) : null); } + // Factory method to convert a user to JSON object Map toJson() { return { 'name': name, @@ -46,8 +62,15 @@ class User { '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 new file mode 100644 index 0000000..36ba306 --- /dev/null +++ b/package/lib/user/user_interface.dart @@ -0,0 +1,115 @@ +import 'dart:async'; + +import '../event/event_repository.dart'; +import '../utils/custom_data.dart'; +import '../utils/logger.dart'; +import 'address_entity.dart'; +import 'token_repository.dart'; +import 'user_entity.dart'; +import 'user_repository.dart'; + +/// `UserInterface` defines methods for interacting with the user repository, +/// handling user identification and login flows, and managing related tokens. +interface class UserInterface { + final UserRepository _repository = UserRepository(); + final EventRepository _eventRepository = EventRepository(); + + /// 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. + /// + /// - [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 { + final String userCurrentToken = mobileToken ?? await token.data ?? ""; + + final address = AddressEntity( + city: city, + street: street, + state: state, + postalCode: postalCode, + country: country, + ); + + final user = UserEntity( + userID: userID, + name: name, + cpf: cpf, + email: email, + gender: gender, + birthday: birthday, + phone: phone, + address: address, + token: userCurrentToken, + customData: customData, + ); + + final version = await customDataVersion; + if (user.customData == null) { + user.customData = version; + } else { + user.customData?.addAll(version); + } + + final resultIdentify = await _repository.identify(user); + await _eventRepository.verifyPendingEvents(); + await token.verifyPendingEvents(); + + return resultIdentify; + } catch (e) { + loggerError('Error identifying user: $e'); + + return false; + } + } + + /// Logs the user into the system by sending a login event to Dito. + /// + /// - [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 String userCurrentToken = mobileToken ?? await token.data ?? ""; + final user = UserEntity(userID: userID, token: userCurrentToken); + + final result = await _repository.login(user); + await _eventRepository.verifyPendingEvents(); + await token.verifyPendingEvents(); + + return result; + } catch (e) { + loggerError('Error identifying user: $e'); + + return false; + } + } +} diff --git a/package/lib/user/user_repository.dart b/package/lib/user/user_repository.dart new file mode 100644 index 0000000..1007a81 --- /dev/null +++ b/package/lib/user/user_repository.dart @@ -0,0 +1,115 @@ +import 'dart:async'; + +import '../api/dito_api_interface.dart'; +import '../proto/sdkapi/v1/api.pb.dart'; +import '../utils/logger.dart'; +import 'user_dao.dart'; +import 'user_entity.dart'; + +final class UserData extends UserEntity { + UserData._internal(); + + static final userData = UserData._internal(); + + factory UserData() => userData; +} + +class UserRepository { + final _userData = UserData(); + final ApiInterface _api = ApiInterface(); + final UserDAO _userDAO = UserDAO(); + + /// This method get a user data on Static Data Object UserData + /// Return a UserEntity Class + UserEntity get data { + return _userData; + } + + /// 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.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 Dito + /// Return bool with true when the identify was successes + Future identify(UserEntity? user) async { + if (user != null) _set(user); + + if (_userData.isNotValid) { + throw Exception('User registration id (userID) is required'); + } + + final activity = ApiActivities().identify(); + final result = await _api.createRequest([activity]).call(); + + if (result >= 400 && result < 500) { + await _userDAO.create(UserEventsNames.login, _userData, activity.id); + return false; + } + + return true; + } + + /// 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(); + final result = await _api.createRequest([activity]).call(); + + if (result >= 400 && result < 500) { + await _userDAO.create(UserEventsNames.login, _userData, activity.id); + return false; + } + + return true; + } + + 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) { + loggerError('Error verifying pending events on notification: $e'); + + rethrow; + } + } +} diff --git a/package/lib/utils/custom_data.dart b/package/lib/utils/custom_data.dart new file mode 100644 index 0000000..dffaf85 --- /dev/null +++ b/package/lib/utils/custom_data.dart @@ -0,0 +1,14 @@ +import 'logger.dart'; + +/// Retrieves the custom data version including the SDK version information. +/// +/// Returns a Future that completes with a Map containing the version information. +Future> get customDataVersion async { + try { + return {"dito_sdk_version": "Flutter SDK - 2.0.0"}; + } catch (e) { + loggerError('Error retrieving package info: $e'); + + return {"dito_sdk_version": "Unknown version"}; + } +} diff --git a/package/lib/utils/http.dart b/package/lib/utils/http.dart deleted file mode 100644 index d67f26a..0000000 --- a/package/lib/utils/http.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:http/http.dart' as http; - -import '../constants.dart'; - -class Api { - Constants constants = Constants(); - - static final Api _instance = Api._internal(); - - factory Api() { - return _instance; - } - - Api._internal(); - - Future post({required url, Map? body}) async { - return await http.post( - url, - body: body, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': constants.platform, - }, - ); - } -} diff --git a/package/lib/utils/logger.dart b/package/lib/utils/logger.dart new file mode 100644 index 0000000..9d695fe --- /dev/null +++ b/package/lib/utils/logger.dart @@ -0,0 +1,7 @@ +import 'package:flutter/foundation.dart'; + +void loggerError(dynamic err) { + if (kDebugMode) { + print("INFO: $err"); + } +} diff --git a/package/pubspec.lock b/package/pubspec.lock deleted file mode 100644 index 318ad46..0000000 --- a/package/pubspec.lock +++ /dev/null @@ -1,490 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _flutterfire_internals: - dependency: transitive - description: - name: _flutterfire_internals - sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7" - url: "https://pub.dev" - source: hosted - version: "1.3.35" - 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" - 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: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" - url: "https://pub.dev" - source: hosted - version: "2.32.0" - firebase_core_platform_interface: - dependency: transitive - description: - name: firebase_core_platform_interface - sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 - url: "https://pub.dev" - source: hosted - version: "5.0.0" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - sha256: "43d9e951ac52b87ae9cc38ecdcca1e8fa7b52a1dd26a96085ba41ce5108db8e9" - url: "https://pub.dev" - source: hosted - version: "2.17.0" - firebase_messaging: - dependency: "direct main" - description: - name: firebase_messaging - sha256: a1662cc95d9750a324ad9df349b873360af6f11414902021f130c68ec02267c4 - url: "https://pub.dev" - source: hosted - version: "14.9.4" - firebase_messaging_platform_interface: - dependency: transitive - description: - name: firebase_messaging_platform_interface - sha256: "87c4a922cb6f811cfb7a889bdbb3622702443c52a0271636cbc90d813ceac147" - url: "https://pub.dev" - source: hosted - version: "4.5.37" - firebase_messaging_web: - dependency: transitive - description: - name: firebase_messaging_web - sha256: "0d34dca01a7b103ed7f20138bffbb28eb0e61a677bf9e78a028a932e2c7322d5" - url: "https://pub.dev" - source: hosted - version: "3.8.7" - 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 641ac3e..bad0acb 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.4 +version: 2.0.0 homepage: https://github.com/ditointernet/sdk_mobile_flutter environment: @@ -13,14 +13,20 @@ dependencies: http: ">=0.13.3" crypto: ">=3.0.1" device_info_plus: ">=9.0.3" - package_info_plus: ">=4.1.0" sqflite: ">=2.3.3" - firebase_messaging: ">=14.9.1" + firebase_messaging: ">=15.0.1" + firebase_core: ">=3.1.0" flutter_local_notifications: ">=17.1.0" - firebase_core: ">=2.30.1" 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 + mockito: ^5.4.4 + dev_dependencies: flutter_test: sdk: flutter flutter_lints: ">=2.0.0" + pub_updater: ">=0.5.0" diff --git a/package/test/dito_sdk_test.dart b/package/test/dito_sdk_test.dart index 22cca08..ceed1df 100644 --- a/package/test/dito_sdk_test.dart +++ b/package/test/dito_sdk_test.dart @@ -1,64 +1,16 @@ -import 'dart:convert'; -import 'dart:io'; - import 'package:dito_sdk/dito_sdk.dart'; import 'package:flutter_test/flutter_test.dart'; -Future testEnv() async { - final file = File('test/.env-test.json'); - final json = jsonDecode(await file.readAsString()); - return json; -} +import 'utils.dart'; -void main() { - final DitoSDK dito = DitoSDK(); - const id = '22222222222'; - - setUp() async { - dynamic env = await testEnv(); - dito.initialize(apiKey: env["apiKey"], secretKey: env["secret"]); - } +final DitoSDK dito = DitoSDK(); +const id = '22222222222'; +void main() { group('Dito SDK: ', () { - test('Send identify', () async { - await setUp(); - - dito.identify( - userID: id, email: "teste@teste.com", customData: {"teste": "Teste"}); - expect(dito.user.id, id); - expect(dito.user.email, "teste@teste.com"); - - final response = await dito.identifyUser(); - expect(response.statusCode, 201); - }); - - test('Send event', () async { - await setUp(); - dito.identify(userID: id); - - final response = await dito.trackEvent(eventName: 'sdk-test-flutter'); - - expect(response.statusCode, 201); - }); - - test('Send mobile token', () async { - await setUp(); - dito.identify(userID: id); - - final response = await dito.registryMobileToken( - token: - "eXb4Y_piSZS2RKv7WeqjW0:APA91bHJUQ6kL8ZrevvO8zAgYIEdtCWSa7RkmszRFdYz32jYblJvOkIiDcpDdqVqZvOm8CSiEHTzljHajvMO66FFxiqteB6od2sMe01UIOwvKrpUOFXz-L4Slif9jSY9pUaMxyqCtoxR"); - - expect(response.statusCode, 200); - }); - - test('Send open notification', () async { - await setUp(); - - final response = await dito.openNotification( - notificationId: '723422', identifier: '1713466024', reference: id); - - expect(response.statusCode, 200); + setUp(() async { + dynamic env = await testEnv(); + dito.initialize(apiKey: env["apiKey"], secretKey: env["secret"]); }); }); } diff --git a/package/test/event/database_test.dart b/package/test/event/database_test.dart new file mode 100644 index 0000000..cbd119a --- /dev/null +++ b/package/test/event/database_test.dart @@ -0,0 +1,87 @@ +import 'dart:convert'; + +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'; +import 'package:uuid/uuid.dart'; + +void main() { + setUpAll(() { + sqfliteFfiInit(); + databaseFactory = databaseFactoryFfi; + }); + + group('EventDatabaseService Tests', () { + final EventDAO eventDAO = EventDAO(); + + tearDown(() async { + await eventDAO.clearDatabase(); + await eventDAO.closeDatabase(); + }); + + test('should insert an event', () async { + final event = EventEntity( + action: 'Test Event', + createdAt: '2024-06-01T12:34:56Z', + revenue: 100.0, + customData: {'key': 'value'}, + ); + + final uuid = Uuid().v4(); + + final success = await eventDAO.create(EventsNames.track, uuid, event: event); + + expect(success, true); + + final events = await eventDAO.fetchAll(); + expect(events.length, 1); + expect(jsonDecode(events.first["event"])["action"], 'Test Event'); + }); + + test('should delete an event', () async { + final event = EventEntity( + action: 'Test Event', + createdAt: '2024-06-01T12:34:56Z', + revenue: 100.0, + customData: {'key': 'value'}, + ); + + final uuid = Uuid().v4(); + + await eventDAO.create(EventsNames.track, uuid, event: event); + await eventDAO.clearDatabase(); + + final events = await eventDAO.fetchAll(); + expect(events.isEmpty, true); + }); + + test('should fetch all events', () async { + final event1 = EventEntity( + action: 'Test Event 1', + createdAt: '2024-06-01T12:34:56Z', + revenue: 100.0, + customData: {'key': 'value1'}, + ); + + final event2 = EventEntity( + action: 'Test Event 2', + createdAt: '2024-06-02T12:34:56Z', + revenue: 200.0, + customData: {'key': 'value2'}, + ); + + final uuid = Uuid().v4(); + final uuid2 = Uuid().v4(); + + await eventDAO.create(EventsNames.track, uuid, event: event1); + await eventDAO.create(EventsNames.track, uuid2, event: event2); + + final events = await eventDAO.fetchAll(); + + expect(events.length, 2); + expect(jsonDecode(events.first["event"])["action"], 'Test Event 1'); + expect(jsonDecode(events.last["event"])["action"], 'Test Event 2'); + }); + }); +} diff --git a/package/test/event/event_test.dart b/package/test/event/event_test.dart new file mode 100644 index 0000000..49dc5e2 --- /dev/null +++ b/package/test/event/event_test.dart @@ -0,0 +1,72 @@ +import 'package:dito_sdk/data/database.dart'; +import 'package:dito_sdk/dito_sdk.dart'; +import 'package:dito_sdk/utils/sha1.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; + +import '../utils.dart'; + +final DitoSDK dito = DitoSDK(); +const id = '22222222222'; + +void main() async { + TestWidgetsFlutterBinding.ensureInitialized(); + dynamic env = await testEnv(); + + + setUpAll(() { + sqfliteFfiInit(); + databaseFactory = databaseFactoryFfi; + dito.initialize(apiKey: env["apiKey"], secretKey: convertToSHA1(env["secret"])); + }); + + group('Events: ', () { + final LocalDatabase database = LocalDatabase(); + + setUp(() async { + await database.database; + }); + + tearDown(() async { + await database.clearDatabase("events"); + await database.closeDatabase(); + }); + + test('Send event without identify', () async { + 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 { + await 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); + expect(result, true); + }); + + test('Send navigation event', () async { + await dito.user.identify(userID: id, email: "teste@teste.com"); + final result = await dito.event.navigate(name: 'home'); + final events = await database.fetchAll("events"); + + expect(events.length, 0); + expect(result, true); + }); + + test('Send event with custom data', () async { + await 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); + + expect(result, true); + }); + }); +} diff --git a/package/test/user/user_test.dart b/package/test/user/user_test.dart new file mode 100644 index 0000000..fc81b7d --- /dev/null +++ b/package/test/user/user_test.dart @@ -0,0 +1,43 @@ +import 'package:dito_sdk/dito_sdk.dart'; +import 'package:dito_sdk/utils/sha1.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../utils.dart'; + +final DitoSDK dito = DitoSDK(); +const id = '22222222222'; + +void main() async { + TestWidgetsFlutterBinding.ensureInitialized(); + dynamic env = await testEnv(); + + + setUpAll(() { + dito.initialize(apiKey: env["apiKey"], secretKey: convertToSHA1(env["secret"])); + }); + + group('User interface', () { + test('User entity start null', () { + expect(dito.user.data.userID, null); + }); + + test('Set User on memory', () async { + 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(userID: id, email: "teste@teste.com"); + expect(result, true); + expect(dito.user.data.id, id); + }); + + test('Send login', () async { + final result = await dito.user.login(userID: id); + expect(result, true); + expect(dito.user.data.id, id); + }); + }); +} diff --git a/package/test/utils.dart b/package/test/utils.dart new file mode 100644 index 0000000..e2adf7f --- /dev/null +++ b/package/test/utils.dart @@ -0,0 +1,9 @@ +import 'dart:convert'; +import 'dart:io'; + +dynamic testEnv() async { + final file = File('test/.env-test.json'); + final json = jsonDecode(await file.readAsString()); + return json; +} + 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"