# OMICALL SDK FOR Flutter

The OmiKit exposes the <a href="https://pub.dev/packages/omicall_flutter_plugin">omicall_flutter_plugin</a> library.

The most important part of the framework is :
- Help to easy integrate with Omicall.
- Easy custom Call UI/UX.
- Optimize codec voip for you.
- Full interface to interactive with core function like sound/ringtone/codec.

### Status
Currently active maintenance and improve performance


### Running
Install via pubspec.yaml:

```
omicall_flutter_plugin: ^latest_version
```

### Configuration

#### Android:

- Add these settings in `build.gradle`:

```
jcenter() 
maven {
    url("https://vihatgroup.jfrog.io/artifactory/omi-voice/")
    credentials {
        username = "downloader"
        password = "Omi@2022"
    }
}
```

```
//in dependencies
classpath 'com.google.gms:google-services:4.3.13'
```

```
//under buildscript
allprojects {
    repositories {
        google()
        mavenCentral()
        jcenter() // Warning: this repository is going to shut down soon
        maven {
            url("https://vihatgroup.jfrog.io/artifactory/omi-voice/")
            credentials {
                username = "downloader"
                password = "Omi@2022"
            }
        }
    }
}
```

You can refer <a href="https://github.com/VIHATTeam/OMICALL-Flutter-SDK/blob/main/OMICALL-Flutter-SDK-Sample/android/build.gradle">android/build.gradle</a> to know more information.

- Add these settings in `app/build.gradle`:

```
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'com.google.gms.google-services'
```

You can refer <a href="https://github.com/VIHATTeam/OMICALL-Flutter-SDK/blob/main/OMICALL-Flutter-SDK-Sample/android/app/build.gradle">android/app/build.gradle</a> to know more information.

- Update AndroidManifest.xml:

```
//need request this permission
<uses-permission android:name="android.permission.INTERNET" />
//add these lines inside <activity>
<intent-filter>
    <action android:name="com.omicall.sdk.CallingActivity"/>
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
//add these lines outside <activity>
<receiver
    android:name="vn.vihat.omicall.omisdk.receiver.FirebaseMessageReceiver"
    android:exported="true"
    android:permission="com.google.android.c2dm.permission.SEND">
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
    </intent-filter>
</receiver>
<service
    android:name="vn.vihat.omicall.omisdk.service.NotificationService"
    android:exported="false">
</service>
```
You can refer <a href="https://github.com/VIHATTeam/OMICALL-Flutter-SDK/blob/main/OMICALL-Flutter-SDK-Sample/android/app/src/main/AndroidManifest.xml">AndroidManifest</a> to know more information.


- We registered permissions into my plugin:
```
<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"
    tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
```

- Setup push notification: Only support Firebase for remote push notification.
  - Add `google-service.json` in `android/app` (For more information, you can refer <a href="https://pub.dev/packages/firebase_core">firebase_core</a>)
  - Add Fire Messaging to receive `fcm_token` (You can refer <a href="https://pub.dev/packages/firebase_messaging">firebase_messaging</a> to setup notification for Flutter)

  - For more setting information, please refer <a href="https://api.omicall.com/web-sdk/mobile-sdk/android-sdk/cau-hinh-push-notification">Config Push for Android</a>

#### iOS(Object-C):

- Assets: Add `call_image` into assets folder to update callkit image. We only support png style.

- Add variables in Appdelegate.h:

```
#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>
#import <OmiKit/OmiKit-umbrella.h>
#import <OmiKit/Constants.h>
#import <UserNotifications/UserNotifications.h>

PushKitManager *pushkitManager;
CallKitProviderDelegate * provider;
PKPushRegistry * voipRegistry;
```

- Edit AppDelegate.m:

```
#import <OmiKit/OmiKit.h>
#import <omicall_flutter_plugin/omicall_flutter_plugin-Swift.h>
```
- Add these lines into `didFinishLaunchingWithOptions`:
```
[OmiClient setEnviroment:KEY_OMI_APP_ENVIROMENT_SANDBOX userNameKey:@"extension" maxCall:1];
provider = [[CallKitProviderDelegate alloc] initWithCallManager: [OMISIPLib sharedInstance].callManager];
voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
pushkitManager = [[PushKitManager alloc] initWithVoipRegistry:voipRegistry];
if (@available(iOS 10.0, *)) {
    [UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;
}

```

```
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
    bool value = [SwiftOmikitPlugin processUserActivityWithUserActivity:userActivity];
    return value;
}
```

-  Add these lines into `Info.plist`:

```
<key>NSCameraUsageDescription</key>
<string>Need camera access for video call functions</string>
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for make Call</string>
```

- Save token for `OmiClient`: if you added `firebase_messaging` in your project so you don't need add these lines.

```
- (void)application:(UIApplication*)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)devToken
{
    // parse token bytes to string
    const char *data = [devToken bytes];
    NSMutableString *token = [NSMutableString string];
    for (NSUInteger i = 0; i < [devToken length]; i++)
    {
        [token appendFormat:@"%02.2hhX", data[i]];
    }
    
    // print the token in the console.
    NSLog(@"Push Notification Token: %@", [token copy]);
    [OmiClient setUserPushNotificationToken:[token copy]];
}

```

*** Only use under lines when added `firebase_messaging` plugin in your project ***
- Setup push notification: We only support Firebase for push notification.
  - Add `google-service.json` in `android/app` (For more information, you can refer <a href="https://pub.dev/packages/firebase_core">firebase_core</a>)
  - Add Firebase Messaging to receive `fcm_token` (You can refer <a href="https://pub.dev/packages/firebase_messaging">firebase_messaging</a> to setup notification for Flutter)

  - For more setting information, please refer <a href="https://api.omicall.com/web-sdk/mobile-sdk/ios-sdk/cau-hinh-push-notification">Config Push for iOS</a>

#### iOS(Swift):

- Assets: Add `call_image` into assets folder to update callkit image. We only support png style.

- Add variables in Appdelegate.swift:

```
import OmiKit
import PushKit
import NotificationCenter

var pushkitManager: PushKitManager?
var provider: CallKitProviderDelegate?
var voipRegistry: PKPushRegistry?
```

- Add these lines into `didFinishLaunchingWithOptions`:

```
OmiClient.setEnviroment(KEY_OMI_APP_ENVIROMENT_SANDBOX, prefix: "", userNameKey: "extension", maxCall: 1)
provider = CallKitProviderDelegate.init(callManager: OMISIPLib.sharedInstance().callManager)
voipRegistry = PKPushRegistry.init(queue: .main)
pushkitManager = PushKitManager.init(voipRegistry: voipRegistry)
```

```
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    var value = SwiftOmikitPlugin.processUserActivity(userActivity: userActivity)
    return value
}
```

-  Add these lines into `Info.plist`:

```
<key>NSCameraUsageDescription</key>
<string>Need camera access for video call functions</string>
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for make Call</string>
```

- Save token for `OmiClient`: if you added `firebase_messaging` in your project so you don't need add these lines.

```
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let deviceTokenString = deviceToken.hexString
    OmiClient.setUserPushNotificationToken(deviceTokenString)
}

extension Data {
    var hexString: String {
        let hexString = map { String(format: "%02.2hhx", $0) }.joined()
        return hexString
    }
}
```

*** Only use under lines when added `firebase_messaging` plugin in your project ***
- Setup push notification: We only support Firebase for push notification.
  - Add `google-service.json` in `android/app` (For more information, you can refer <a href="https://pub.dev/packages/firebase_core">firebase_core</a>)
  - Add Firebase Messaging to receive `fcm_token` (You can refer <a href="https://pub.dev/packages/firebase_messaging">firebase_messaging</a> to setup notification for Flutter)

  - For more setting information, please refer <a href="https://api.omicall.com/web-sdk/mobile-sdk/ios-sdk/cau-hinh-push-notification">Config Push for iOS</a>

*** Important release note ***
```
We support 2 environments. So you need set correct key in Appdelegate.
- KEY_OMI_APP_ENVIROMENT_SANDBOX support on debug mode
- KEY_OMI_APP_ENVIROMENT_PRODUCTION support on release mode
- Visit on web admin to select correct enviroment.   
```

## Implement
- Set up for Firebase:

```
await Firebase.initializeApp();
// If you only use Firebase on Android. Add these line `if (Platform.isAndroid)`
// Because we use APNS to push notification on iOS so you don't need add Firebase for iOS.
```
- Important function.
  - Start Serivce: OmiKit need start services and register some events.
    ```
    //Call in the root widget
    OmicallClient.instance.startServices();
    ```
  - Create OmiKit: OmiKit need userName, password, realm, host to init enviroment. ViHAT Group will provides these information for you. Please contact for my sales:
    ```
    await OmicallClient.instance.initCall(
      userName: "", 
      password: "",
      realm: "",
      host: "",
      isVideo: true/false,
    );
    ```
  - Create OmiKit With ApiKey: OmiKit need apikey, username, user id to init enviroment. ViHAT Group will provides api key for you. Please contact for my sales:
    ```
     await OmicallClient.instance.initCallWithApiKey(
      usrName: "",
      usrUuid: "",
      isVideo: true/false,
      apiKey: "",
    );
    ```
  - Get call when user open app from killed status(only iOS):
    ```
    final result = await OmicallClient.instance.getInitialCall();
    ///if result is not equal False => have a calling.
    ```
  - Config push notification: With iOS, I only support these keys: `prefixMissedCallMessage`, `missedCallTitle`, `userNameKey`. With Android, We don't support `missedCallTitle`:
    ```
    OmicallClient.instance.configPushNotification(
      notificationIcon : "calling_face", //notification icon on Android
      prefix : "Cuộc gọi tới từ: ",
      incomingBackgroundColor : "#FFFFFFFF",
      incomingAcceptButtonImage : "join_call", //image name
      incomingDeclineButtonImage : "hangup", //image name
      backImage : "ic_back", //image name: icon of back button
      userImage : "calling_face", //image name: icon of user default
      prefixMissedCallMessage: 'Cuộc gọi nhỡ từ' //config prefix message for the missed call
      missedCallTitle: 'Cuộc gọi nhỡ', //config title for the missed call
      userNameKey: 'uuid', //we have 3 values: uuid, full_name, extension
      channelId: 'channelid.callnotification' // need to use call notification
    );
    //incomingAcceptButtonImage, incomingDeclineButtonImage, backImage, userImage: Add these into `android/app/src/main/res/drawble`
    ```
- Upload token: OmiKit need FCM for Android and APNS to push notification on user devices.
  ```
  final token = await FirebaseMessaging.instance.getToken();
  String? apnToken;
  if (Platform.isIOS) {
      apnToken = await FirebaseMessaging.instance.getAPNSToken();
  }
  String appId = 'Bundle id on iOS/ App id on Android'
  await OmicallClient.instance.updateToken(
    fcmToken: token,
    apnsToken: apnToken,
  );
  ```

- Other functions:
  -  Call with phone number (mobile phone or internal number):
    ```
    final result = await OmicallClient.instance.startCall(
        phone, //phone number
        _isVideoCall, //call video or audio. If true is video call. 
    );
    //we will return OmiStartCallStatus with:
    - invalidUuid: uuid is invalid (we can not find on my page)
    - invalidPhoneNumber: sip user is invalid.
    - samePhoneNumber: Can not call same phone number.
    - maxRetry: We try to refresh call but we can not start your call.
    - permissionDenied: Check audio permission.
    - couldNotFindEndpoint: Please login before make your call.
    - accountRegisterFailed: We can not register your account.
    - startCallFailed: We can not start you call.
    - startCallSuccess: Start call successfully.
    - haveAnotherCall: We can not start you call because you are joining another call.
    ```
  -  Call with UUID (only support with Api key):
    ```
    final result = OmicallClient.instance.startCallWithUUID(
        uuid, //your user id
        _isVideoCall, //call video or audio. If true is video call. 
    );
    // Result is the same with startCall
    ```
  - Accept a call:
    ```
    OmicallClient.instance.joinCall();
    ```
  - End a call: We will push a event `endCall` and return call information for you.
    ```
    OmicallClient.instance.endCall().then((value) {
        //value is call information
    });
    Sample output:
    {
       "transaction_id":ea7dff38-cb1e-483d-8576...........,
       "direction":"inbound",
       "source_number":111,
       "destination_number":110,
       "time_start_to_answer":1682858097393,
       "time_end":1682858152181,
       "sip_user":111,
       "disposition":"answered"
    }
    ```
  - Toggle the audio: On/off audio a call
    ```
    OmicallClient.instance.toggleAudio();
    ```
  - Toggle the speaker: On/off the phone speaker
    ```
    OmicallClient.instance.toggleSpeaker();
    ```
  - Send character: We only support `1 to 9` and `* #`.
    ```
    OmicallClient.instance.sendDTMF(value);
    ```
  - Get current user information:
    ```
    final user = await OmicallClient.instance.getCurrentUser();
    Output Sample:  
    {
        "extension": "111",
        "full_name": "chau1",
        "avatar_url": "",
        "uuid": "122aaa"
    }
    ```
  - Get guest user information:
    ```
    final user = await OmicallClient.instance.getGuestUser();
    Output Sample:  
    {
        "extension": "111",
        "full_name": "chau1",
        "avatar_url": "",
        "uuid": "122aaa"
    }
    ```
  - Get user information from sip:
    ```
    final user = await OmicallClient.instance.getUserInfo(phone: "111");
    Output Sample:  
    {
        "extension": "111",
        "full_name": "chau1",
        "avatar_url": "",
        "uuid": "122aaa"
    }
    ```
  - Logout:
    ```
    OmicallClient.instance.logout();
    ```
- Video Call functions: Support only video call, We need enable video in `init functions` and `start call` to implements under functions.
  - Switch front/back camera: We use the front camera for first time.
  ```
  OmicallClient.instance.switchCamera();
  ```
  - Toggle a video in video call: On/off video in video call
  ```
  - OmicallClient.instance.toggleVideo();
  ```
  - Register video event: Need to listen remote video ready
  ```
   OmicallClient.instance.toggleVideo();
  ```
  - Remove video event: Need to remove video event
  ```
  OmicallClient.instance.removeVideoEvent();
  ```
  - Local Camera Widget: Your camera view in a call
  ```
  LocalCameraView(
    width: double.infinity,
    height: double.infinity,
    onCameraCreated: (controller) {
      _localController = controller;
      //we will return the controller to call some functions.
    },
  )
  ```
  - Remote Camera Widget: Remote camera view in a call
  ```
  RemoteCameraView(
     width: double.infinity,
     height: double.infinity,
     onCameraCreated: (controller) {
       _remoteController = controller;
     },
  )
  ```
  - More function: Refresh camera
  ```
  //camera controller receive when you create camera widget.
  RemoteCameraController? _remoteController;
  LocalCameraController? _localController;
  //refresh remote camera
  void refreshRemoteCamera() {
    _remoteController?.refresh();
  }
  //refresh remote camera
  void localRemoteCamera() {
    _localController?.refresh();
  }
  ```

- Event listener:
  - Important event `callStateChangeEvent`: We provide it to listen call state change.
 ```
 OmicallClient.instance.controller.callStateChangeEvent
 //OmiAction have 2 variables: actionName and data
 ```
    - Action Name value: 
        - `onCallStateChanged`: Call state changed.
        - `onSwitchboardAnswer`: Switchboard sip is listening. 
    + onCallStateChanged is call state tracking event. We will return status of state. Please refer `OmiCallState`.
    + Incoming call state lifecycle: incoming(receive on foreround state) -> early -> connecting -> confirmed -> disconnected
    + Outgoing call state lifecycle: calling -> early (call created) -> connecting -> confirmed -> disconnected
    + onSwitchboardAnswer have callback when employee answered script call.
- Other events:
  - Call Quality event: Listen call quality event changed
  ```
  OmicallClient.instance.setCallQualityListener((data) {
      final quality = data["quality"] as int;
  });
  //we return `quality` key with: 0 is GOOD, 1 is NORMAL, 2 is BAD
  ```
  - Mic event: Listen on/off mic in a call
  ```
  OmicallClient.instance.setSpeakerListener((data) {
      setState(() {
        isSpeaker = data;
      });
  });
  //data is current speaker status.
  ```
  - Mute event: Listen on/off muted in a call
  ```
  OmicallClient.instance.setMuteListener((data) {
      setState(() {
        isMuted = data;
      });
  });
  //data is current muted status.
  ```
  - Remote video ready: Listen remote video ready.
  ```
  OmicallClient.instance.setVideoListener((data) {
      refreshRemoteCamera(); => need refresh camera
      refreshLocalCamera(); => need refresh camera
  });
  ```
  - User tab a missed call notification:
  ```
  OmicallClient.instance.setMissedCallListener((data) {
      final String callerNumber = data["callerNumber"];
      final bool isVideo = data["isVideo"];
      makeCallWithParams(context, callerNumber, isVideo);
  });
  // data is Map. Data has 2 keys: callerNumber, isVideo
  ```
  - User tab a call log (only `iOS`):
  ```
  OmicallClient.instance.setCallLogListener((data) {
      final callerNumber = data["callerNumber"];
      final isVideo = data["isVideo"];
      makeCallWithParams(
        context,
        callerNumber,
        isVideo,
      );
  });
  // data is Map. Data has 2 keys: callerNumber, isVideo
  ```