# 🧙 Magify Unity3d SDK
[![sdk](https://img.shields.io/badge/sdk-4.6.10-green)](https://github.com/app-craft/magify-unity-plugin)

Magify is an application campaigns system.  
It determines which campaign will be shown on specific event in application.  
Supported campaign types: subscription and in-app, rate review, promo, interstitial,rewarded video and banner.  
Example of events: start of the session, purchase button click, opening of game screen.  
Campaigns priorities and conditions are setuped in dashboard.  
Staging dashboard: https://brotherhood.jx-staging.middle-earth.io  
  
## Contents
- [🧙 Magify Unity3d SDK](#-magify-unity3d-sdk)
  - [Contents](#contents)
  - [Installation](#installation)
  - [Usage](#usage)
  - [Configuration](#configuration)
  - [Analytics](#analytics)
    - [Track Installs Analytics](#track-installs-analytics)
  - [Level information](#level-information)
  - [Content handling](#content-handling)
    - [Content list](#content-list)
    - [Earliest content](#earliest-content)
    - [Latest content](#latest-content)
  - [Transactions](#transactions)
  - [Features handling](#features-handling)
  - [Campaigns request](#campaigns-request)
  - [Campaign types](#campaign-types)
    - [Subscriptions](#subscriptions)
    - [InApp](#inapp)
    - [Rate Review](#rate-review)
    - [Promo](#promo)
    - [Interstitial](#interstitial)
    - [Banner](#banner)
    - [Rewarded Video](#rewarded-video)
    - [Notification](#notification)
    - [Bonus](#bonus)
    - [Mixed](#mixed)
  - [Presenters](#presenters)
  - [Postprocessor](#postprocessor)


## Installation 
1) Install [The External Dependency Manager for Unity](https://developers.google.com/unity/archive#external_dependency_manager_for_unity)
2) Add ```Magify SDK for Unity``` to the ```manifest.json```.

OR Download latest [Magify SDK for Unity](https://github.com/Magify-LTD/magify-unity/releases/download/4.6.10/com.magify.unity-sdk-4.6.10.tgz) and [Magify Service for Unity](https://github.com/Magify-LTD/magify-unity/releases/download/4.6.10/com.magify.unity-sdk-service-4.6.10.tgz) to ```Packages``` folder and add it as local package.

```
{
  "dependencies": {
    "com.magify.unity-sdk": "file:com.magify.unity-sdk-4.6.10.tgz",
    "com.magify.unity-sdk-service": "file:com.magify.unity-sdk-service-4.6.10.tgz",
    ...
  },
}
```
3) Resolve dependencies using ```The External Dependency Manager for Unity```
```
Assets ➞ External Dependency Manager ➞ Android Resolver ➞ Force Resolve
```
## Usage
1) Get default application config json for current version (download it in application dashboard or ask your manager) and put it in  ```StreamingAssets``` folder.
2) Prepare [MagifyConfig](#configuration) and pass it to `MagifyManager.Initialize`

```C#
var config = new MagifyConfig
{
    AppNameGP = "APP_NAME_ANDROID",
    AppNameIOS = "APP_NAME_IOS",
    ConfigPathGP = "default-config-android.json",
    ConfigPathIOS = "default-config-ios.json",
    Environment = Environment.Staging,
    SubscriptionStatus = SubscriptionStatus.Inactive,
    AuthorizationStatus = AuthorizationStatus.Unauthorized,
    IsGdprApplied = false,
    EditorMoq = new MagifyTestEditorMoq(),
    IsDeveloperMode = false,
    EditorDevice = EditorDevice.GetDefaultAndroidDevice(),
    PresenterSettings = null,
    IsLoggingEnabled = true,
    RevenuePerCountryPath = "revenue-per-country.json",
    RevenueLevelsPath = "revenue-levels.json",
    DefaultCurrencyRatesPath = "currency-rates.json",
    SubscriptionMultipliersPath = "trial-conversion.json",
};

MagifyManager.Initialize(config);
```
---

In additional to `IsLoggingEnabled` you can add a `MAGIFY_VERBOSE_LOGGING` scripting define symbol to enable verbose logging. 
Be sure to remove it in production builds because it may impact performance.
```
Project Settings ➞ Player ➞ iOS/Android ➞ Scripting Define Symbols
```
---

**⚠️ IMPORTANT Conversion value updates works only on iOS 14+** To test integration see logs produced by application. All logs relates to this feature contain presfix "Conversion:". Example:
```
2021-03-25 10:12:51.262333+0100 numberama[15539:3778819] [Magify] (
    "Conversion: Increment with banner by 2.229e-05"
)
2021-03-25 10:12:51.265949+0100 numberama[15539:3778819] [Magify] (
    "Conversion: Conversion value did set to: 1, sum: 4.458e-05"
)
```

3) When you want to reload config from server call  ```Sync()``` method. For example, you can do it at the start of every session:
```C#
void OnApplicationPause(bool paused)
{
      if (!paused && MagifyManager.Initialized)
      {
          MagifyManager.Sync();
      }
}
``` 
4) In case you need to receive updates when remote config was successfully loaded, subscribe to events using `MagifyManager.OnConfigLoaded` 
Example:
```C#
MagifyManager.OnConfigLoaded += () =>
{
    // Do something
};
```
5) On iOS If your app asked user to grant cross-app tracking permission (use their IDFA), you should pass it to magify using ```SetAttStatus(bool authorized)``` method. If ATT status changed magify will delete token and request it again with campaigns.
```C#
MagifyManager.SetAttStatus(authorized)
```

6) Connect with 3rd party services. If your app has Appsflyer, Adjust, RevenueCat or another sdks, you should initialize them with ClientId provided by Magify.
```C#
var clientId = MagifyManager.ClientId;

// Adjust
AppsFlyer.setCustomerUserID(clientId);

// RevenueCat
var purchases = GetComponent<Purchases>();
purchases.appUserID = clientId;

// Adjust
var adjustConfig = new AdjustConfig(appToken, env);
adjustConfig.setExternalDeviceId(clientId);
Adjust.start(adjustConfig);

// Crashlytics
Crashlytics.SetUserId(clientId);

// GameAnalytics
GameAnalytics.SetCustomId(clientId);

// Applovin MAX
MaxSdk.SetUserId(clientId);

// etc.
```

7)  If your application has referrer identifier (e.g. Facebook Deferred Deep Link) set it to magify at start, as soon as it was fetched
```C#
MagifyManager.ReferrerId = "referrerId"
```

8) Optionally you can track attribution info if any. At any time when info was received, pass network, campaign and ad group to magify (all values are optional):
```C#
MagifyManager.SetMediaSource(networkName, campaignName, adGroup);
```
9) Optionally to receive mailing, send your user email value to magify using:
```C# 
MagifyManager.SetUserEmail(email)
```

## Configuration
Property | Required | Default | Description
--- | --- | --- | --- |
AppNameGP / AppNameIOS | Yes | null | The name of your application (take it from application dashboard or ask your manager). It will be different for each platform `MagifyManager.Environment`
ConfigPathGP / ConfigPathIOS | Yes | null | Path to the default application config. Relative to the `StreamingAssets` folder 
Environment | No | Production | Default environment. You can change it later using `MagifyManager.Environment`. Make sure this property is set to `Production` before publishing your app to stores.
SubscriptionStatus | No | Inactive | If your application has subscription, then you must setup this property. *⚠️ IMPORTANT: Update subscription status not only during sdk setup, but also when it was changed during application lifecycle using `MagifyManager.SubscriptionStatus`.*
AuthorizationStatus | No | Unauthorized | If your application has authorization, you must setup this property. *⚠️ IMPORTANT: Update authorization status not only during sdk setup, but also when it was changed using `MagifyManager.AuthorizationStatus`.*
IsGdprApplied | No | null | State of user consent to GDPR. If the value is null, default value(false) or saved value from previous session will be used.
IsLoggingEnabled | No | true | Default logging status. You can change it later using `MagifyManager.IsLoggingEnabled`
EditorMoq | No | null | Use it for emulating campaigns in editor
IsDeveloperMode | No | false | If you want to use only moq set this flag to 'true'
EditorDevice | No | null | Use it for emulating some device in the Unity Editor
RevenuePerCountryPath | Yes | null | Path to `iOS` specific conversion tacker meta file (ask your manager). Relative to the `StreamingAssets` folder 
RevenueLevelsPath | Yes | null | Path to `iOS` specific conversion tacker meta file (ask your manager). Relative to the `StreamingAssets` folder 
DefaultCurrencyRatesPath | Yes | null | Path to `iOS` specific conversion tacker meta file (ask your manager). Relative to the `StreamingAssets` folder 
SubscriptionMultipliersPath | Yes | null | Path to `iOS` specific conversion tacker meta file (ask your manager). Relative to the `StreamingAssets` folder 

## Analytics

 - ```TrackAppLaunch()``` - use to track app start event.

 - ```TrackCustomEvent()``` use to track any event in app.

### Track Installs Analytics

In order to properly track installed apps (model `TrackInstalls`) in iOS platform, application must include array of app ids you want to track in `info.plist` file by `LSApplicationQueriesSchemes` key.

## Level information

 - ```SetGameMode()``` - use to set game mode.

 - ```SetGameLevel()``` use to set current level.

 - ```SetGameMaxLevel()``` use to set max level.

## Content handling

### Content list  

To get list of content, use method `GetContentList()` passing key of group, key of content and optionally list of tags. Example:
```C#
var list = MagifyManager.Content.GetContentList("group_key", "content_key", new List<string>());
```
  
### Earliest content  

To find earliest content item, use method `GetEarliestContent()` passing key of group and key of content. Example:
```C#
var contentItem = MagifyManager.Content.GetEarliestContent("group_key", "content_key");
```
  
### Latest content  

To find latest content item, use method `GetLatestContent()` passing key of group and key of content. Example:
```C#
var contentItem = MagifyManager.Content.GetLatestContent("group_key", "content_key");
```

## Transactions
To send transactions info to magify use following methods:

Incomes
```C#
void MagifyManager.TrackIncomeTransaction(string source, List<BonusInfo> bonuses, ProductInfo product = null);
```
```source``` - text identifier for income source. For example: inApp/reward/daily/bonus/subscription etc.

```bonuses``` - list of incoming bonuses within transaction, includes bonus name, income quantity and finalBalance - total after current transaction

```product``` - product info includes productId, price and currency. Optional, in case transaction was within any magify campaign (inapp, reward, bonus, subscription)

Expenses
```C#
void MagifyManager.TrackExpenseTransaction(List<BonusInfo> bonuses);
```
```bonuses``` - list of expense bonuses within transaction


## Features handling
`Features` in terms of `Gandald SDK` are similar to remote config. You have 4 main methods of getting values from it:

```C#
// booleans
bool MagifyManager.Features.TryGetBool(string featureName, out bool result)
bool MagifyManager.Features.GetBool(string featureName)

// whole numbers
bool MagifyManager.Features.TryGetLong(string featureName, out long result)
long MagifyManager.Features.GetLong(string featureName)

// fractional numbers
bool MagifyManager.Features.TryGetDouble(string featureName, out double result)
double MagifyManager.Features.GetDouble(string featureName)

// strings
bool MagifyManager.Features.TryGetString(string featureName, out string result)
string MagifyManager.Features.GetString(string featureName)
```

You can subscribe to `MagifyManager.Features.OnFeaturesUpdated` if you need to listen for feature updates from the backend.

Example:
```C#
MagifyManager.Features.OnFeaturesUpdated += () =>
{
    if (MagifyManager.Features.TryGetBool(featureName, out var boolResult))
    {
        Debug.Log($"The boolean value for {featureName} is {boolResult}");
    }
    else
    {
        Debug.Log($"No boolean value for {featureName}");
    }
    if (MagifyManager.Features.TryGetString(featureName, out var stringResult))
    {
        Debug.Log($"The string value for {featureName} is {stringResult}");
    }
    else
    {
        Debug.Log($"No string value for {featureName}");
    }
};
```

## Campaigns request  
  
  To request a campaign, call ```CampaignFor()``` method of MagifyManager passing an event name to it and optionally event parameters.
  Parameters can be used to specify the context of event. Ask your configuration manager for what events you should specify with parameters. For example, application has event 'sessionStart', so you can request campaign with additional parameters as 'sessionNumber' etc. Type of parameters depends on event configuration.
  Example:  
```C#
var eventParams = new Dictionary<string, object>
{
    {"sessionNumber", 3}
};
Magify.ICampaign campaign = MagifyManager.CampaignFor("sessionStart", eventParams);
```    
  and than handle returned campaign:  
```C#
if (campaign == null)
{
    // Do nothing
    return;
}

if (campaign is BannerCampaign)
{
    // Show banner
    return;
}

if (campaign is InterstitialCampaign)
{
    // Show Interstitial
    return;
}

if (campaign is RewardedVideoCampaign)
{
    // Show Rewarded video
    return;
}

if (campaign is PromoCampaign)
{
    // Show Promo
    return;
}

if (campaign is RateReviewCampaign)
{
    // Show Rate Review
    return;
}

if (campaign is SubscriptionCampaign)
{
    // Show Subscription campaign
    return;
}

if (campaign is InAppCampaign)
{
    // Show InApp campaign
    return;
}

if (campaign is BonusCampaign)
{
    // Show Bonus campaign
    return;
}

if (campaign is MixedCampaign)
{
    // Show Mixed campaign
    return;
}

``` 
  
## Campaign types
Next campaign types are supported by framework:
- subscription
- inapp
- rateReview
- promo
- interstitial
- banner  
- rewardedVideo
    
### Subscriptions  
If magify returned subscription campaign, you should show subscription screen with specified screenId and configure it with provided products list.
Products list contains instances of  ```SubsProduct```.  
```SubsProduct``` has productId and context.
- ProductID: identifier of subs product
- Context: dictionary of custom attributes, that can be used for customization of product display on screen.

```C#
Magify.ICampaign campaign = MagifyManager.CampaignFor("premiumTapped");
if (campaign is SubscriptionCampaign)
{
    // Show Subscription screen
    SubscriptionCampaign subscriptionCampaign = (SubscriptionCampaign)campaign;
    showSubscriprionScreen(subscriptionCampaign.screenID, subscriptionCampaign.Products);
    return;
}
void showSubscriprionScreen(string screenId, List<SubsProduct> products)
{
    /* Show subscription screen code */
}
```        
**Analytics**  
When subscription screen is shown, you should track impression:
```C#
MagifyManager.TrackImpression(subscriptionCampaign.Type);
```    
When subscribe button clicked on subscription screen, you should track click and pass productId that was selected:
```C#
MagifyManager.TrackProductClickFor(subscriptionCampaign.Type, productId);
```     
When subscription purchased, you should track subscription activation passing: trial status, productId, price, currency and period:
```C#
bool isTrial = ...;
string productId = ...;
string price = ...;
string currency = ...;
string period = ...; // Can be week/month/year or null. null is for cases where period is unknown.
MagifyManager.TrackSubscriptionActivation(isTrial, productId, price, currency, period);
```  
     
### InApp  
Campaign is similar to subscription campaign, excepts that user purchase not subscription, but in-apps. 
If inapp campaign has `Custom` screen type you should show your own custom screen.
When showing in-apps screen you should configure it with specified in-app products list.
Products list contains instances of  ```InAppProduct```.  
```InAppProduct``` has productId, type and context.
- ProductID: identifier of store inapp,  
- Context: dictionary of custom attributes, that can be used for customization of product display on screen.
- IsConsumable: bool field (Consumable|NonConsumable)

```C#
Magify.ICampaign campaign = MagifyManager.CampaignFor("diamondsStore");
if (campaign is InAppCampaign)
{
    // Show In-apps store screen
    InAppCampaign inAppCampaign = (InAppCampaign)campaign;
    List<InAppProduct> products = inAppCampaign.Products;
    if (inAppCampaign.Screen.Type == ScreenType.Custom)
    {
        string screenId = inAppCampaign.Screen.ScreenId;
        ShowInAppStoreScreen(screenId, products);
    }
    return;
}

private void ShowInAppStoreScreen(string screenId, List<InAppProduct> products)
{
    /* Show in-apps store screen code */
}
```

If campaign has `NoUI` screen type you should start purchase flow (open system purchase dialog) without any UI, as if user clicked product from UI creative.
```C#
InAppCampaign inAppCampaign = (InAppCampaign)campaign;
List<InAppProduct> products = inAppCampaign.Products;
if (inAppCampaign.Screen.Type == ScreenType.NoUI)
{
    // Track campaign impression and product click
    // Request purchase system dialog
}
```

If inapp campaign has `Creative` screen type you should show it using ```PresentCampaign``` method.  
Method accepts 6 parameters:
- **campaign** that should be shown,
- **timeout** - time for creative image to load. If it won't be loaded before that time, creative won't be shown,
- **closeAfterClick** - close presenter after first click ot not
- **showCallback** - called right before creative show animation. Can be null
- **clickCallback** - called when the player clicks on the creative. You should open system purchase dialog here. Can be null
- **closeCallback** - result of user interaction with inapp creative. Callback parameter will be `true` if the player closed the creative by clicking the `close` button. Otherwise, it will be `false`. Can be null
- **failCallback** - called in case of any error. Can be null
If user clicked creative you should start purchase process. Callback parameter is the error message with reason
```C#
MagifyManager.PresentCampaign(campaign, 3,
() => // showCallback
{
    Debug.Log("[MagifyTest]: PresentCampaign has been shown");
}
(itemIndex) => // clickCallback
{
    Debug.Log("PresentCampaign clicked");
    
    // Request purchase system dialog

    // if inapp purchsed:
    {
        var productId = /* your product id. */ campaign.Products[0].ProductID;
        var price = /* your product price */ "1.99";
        var currency = /* user store currency */ "USD";
        MagifyManager.TrackInAppFor(productId, price, currency);
    }

    MagifyManager.ClosePresentedCreative();
}, 
byUser => // closeCallback
{
    Debug.Log($"PresentCampaign closed. ByUser: {byUser}");
}, 
reason => // failCallback
{
    Debug.LogError($"Failed to show {nameof(InAppCampaign)} - {campaign.Name}");
    Debug.LogError($"PresentCampaign failed with reason - {reason}");
});
```

You can close inapp creative screen manually or calling method ```ClosePresentedCreative```. In this case closeCallback will be called with `false` parameter.

**Analytics**  
 When custom screen is shown or NoUI creative, you should track impression:
 ```C#
 MagifyManager.TrackImpression(campaign.Type);
 ```    
 When purchase button clicked from custom screen or NoUI creative you should track click and pass productId that was selected (for NoUI creative just take first item from products list):
 ```C#
 MagifyManager.TrackProductClickFor(campaign.Type, productId);
 ```    
 When in-app purchased, you should track in-app passing to productId, price and currency:
 ```C#
 string productId = ...;
 string price = ...;
 string currency = ...;
 MagifyManager.TrackInAppFor(productId, price, currency);
```  

In cases you have unhandled external inapps, when you open app (performed purchase from system store, activate promocode or restore purchase), use list of default product context.
```C#
List<InAppProduct> products = MagifyManager.inAppProducts;
```

**Analytics**  
When consume external inapps you should also track inapp purchase:
```C#
MagifyManager.TrackExternalInAppFor(productId, price, currency);
```    
    
### Rate Review  
If magify returned rate review campaign:
- on iOS - you should request system rate review.  
- on Android - you should show own review screen
```C#
Magify.ICampaign campaign = MagifyManager.CampaignFor("rateReview");
if (campaign is RateReviewCampaign)
{
    RequestReview();
    return;
}

private void RequestReview()
{
#if UNITY_IOS
  UnityEngine.iOS.Device.RequestStoreReview();
#elif UNITY_ANDROID
  // Show screen
#endif
}
```
**Analytics**  
As iOS doesn't allow to know if rate review were shown, you should track impression every time you requesting review, for Android track impression only when screen was shown:
```C#
MagifyManager.TrackImpression(campaign.Type);
```
When review button was clicked from screen, track click:
 ```C#
 MagifyManager.TrackClickFor(campaign.Type);
 ```    
      
### Promo  
If campaign has `Custom` screen type you should show your own custom screen.
```C#
Magify.ICampaign campaign = MagifyManager.CampaignFor("gameStart");
if (campaign is PromoCampaign)
{
    // Show custom promo screen
    PromoCampaign promoCampaign = (PromoCampaign)campaign;
    if (promoCampaign.Screen.Type == ScreenType.Custom)
    {
        string screenId = promoCampaign.Screen.ScreenId;
        // Show custom screen creative by screenId
        MagifyManager.TrackImpression(campaign.Type);
    }
    return;
}
```

If campaign has `NoUI` screen type you should start redirection without any UI.
```C#
PromoCampaign promoCampaign = (PromoCampaign)campaign;
if (promoCampaign.Screen.Type == ScreenType.NoUI)
{
    // Track campaign impression and click event
    MagifyManager.TrackImpression(campaign.Type);
    MagifyManager.TrackClickFor(campaign.Type);

    // Start redirection flow            
    MagifyManager.OpenCrossPromoCampaign(campaign);
}
```

If campaign has `Creative` screen type , you should show it using  ```PresentCampaign```  method.  
Method accepts 6 parameters:
- **campaign** that should be shown,
- **timeout** - time for promo to load. If it won't be loaded before that time, promo won't be shown,
- **showCallback** - called right before creative show animation. Can be null
- **clickCallback** - called when the player clicks on the creative. You can use it to redirect user to presented destination. Can be null
- **closeCallback** - result of user interaction with inapp creative. Callback parameter will be `true` if the player closed the creative by clicking the `close` button. Otherwise, it will be `false`. Can be null
- **failCallback** - called in case of any error. Can be null
If user clicked promo you should open promo using method ```OpenCrossPromoCampaign```
```C#
MagifyManager.PresentCampaign(campaign, 3.0, 
() => // showCallback
{
    Debug.Log("[MagifyTest]: PresentCampaign has been shown");
}
() => // clickCallback
{
    MagifyManager.PresentCampaign(campaign);
}, null, null);
```
**⚠️  IMPORTANT Make sure you have added ulr-schemes of promoted apps to project Info.plist file for iOS builds. See [Postprocessor](#postprocessor) section with example**

**Analytics**  
 When custom screen is shown or NoUI creative, you should track impression:
 ```C#
 MagifyManager.TrackImpression(campaign.Type);
 ```    
 When button clicked from custom screen or directly for NoUI creative you should track click event:
 ```C#
 MagifyManager.TrackClickFor(campaign.Type);
 ```    

### Interstitial  
If magify returned interstitial campaign, you should request and show interstitial to user.
```C#
Magify.ICampaign campaign = MagifyManager.CampaignFor("backToMenu");
if (campaign is InterstitialCampaign)
{
    /* Request and show interstitial */
    return;
}
```
```InterstitialCampaign``` has field  ```splashscreen``` which says if there should be any splashscreens shown before showing interstitial.  
Splashscreen has type and paramaters for timeout, minimum/maximum and exact show time.  
Possible values of splashscreen type is:
- **No** - no screens should be shown. Parameters that should be handled: timeout. Load an interstitial and show it if it loaded before the timeout. If interstitial loaded later hat timeout time, don't show it.
- **Popup** - splashscreen is shown only if interstitial is not yet loaded when requested. You should start interstitial loading, and when it loaded and ready to show you should how popup informing user that now will be interruption for an ads.
If interstitial is already loaded when requested, just show it immediately.  
Parameters that should be handled: timeout and exactTime.  
Timeout - time available for interstitial loading.  
ExactTime - how long popup should be shown.  
MaxTime - max time waiting ad loading when popup will not be shown.
isForced - forced show popup creative before inter.

- **LoaderSplash** - show splashscreen during interstitial loading. Parameters that should be handled: minTime and maxTime.  
MinTime - minimum time that splash should be shown.
MaxTime - maximum time that splash can be shown. If interstitial not loaded before that time, don't show it.
```C#
InterstitialSplashscreen splash = campaign.splashscreen;
switch (splash.type)
{
    case SplashscreenType.No:
        /* Request and show ads */
        break;
    case SplashscreenType.Popup:
        /* Request ads, when it loaded - show popup, than show interstitial */
        break;
    case SplashscreenType.LoaderSplash:
        /* Show splash, load interstitial, that hide splash and show interstitial */
        break;
}
```
**Interstitials limits**  
  
Interstitials can have limits for show during session. Limitations include:  
    - ```MagifyManager.Limits.InterstitialsPerSession``` - number of interstitials per session  
    - ```MagifyManager.Limits.InterstitialInterval``` - interval between two interstitial shown.  
    - ```MagifyManager.Limits.RewardInterstitialInterval``` - interval between reward and interstitial shown.  
You should get this values from magify and consider that when showing interstitials.  
  
**Analytics**  

***MoPub***  
Impression:

 Implement Impression-Level Revenue Data.  
 Integration guide: https://developers.mopub.com/publishers/unity/impression-data/  
 In ```OnImpressionTrackedEvent(string adUnitId, MoPub.ImpressionData impressionData)``` method track impression to Magify.  
 Convert impressionData to ```Dictionary<string, object>``` and pass to tracking method:
 ```C#
private void OnImpressionTrackedEvent(string adUnitId, MoPub.ImpressionData impressionData)
{
   if (adUnitId == /* your application interstitial unitId */)
   {
        string data = impressionData.JsonRepresentation;
        var impressionInfo = Magify.MiniJSON.Json.Deserialize(data) as Dictionary<string, object>;
        MagifyManager.TrackAdsImpression(CampaignType.Interstitial, impressionInfo);
   }
}
```
Click:

To track interstitial click implement MoPub callback  ```OnInterstitialClickedEvent (string adUnitId)```  and track click to Magify:  
```C#
MagifyManager.TrackAdsClickFor(CampaignType.Interstitial);
```

***Applovin Max***  
Impression:

Implement ```MaxSdkCallbacks.OnInterstitialDisplayedEvent``` callback track impression to Magify
```C#
private void OnInterstitialDisplayedEvent(string adUnitId, string adNetworkName)
{
   if (adUnitId == /* your application interstitial unitId */)
   {            
       var impressionInfo = new Dictionary<string, string>()
       {
           { "tracker_type", "applovin" },
           { "adUnitId", adUnitId },
           { "networkName", adNetworkName }
       };
       MagifyManager.TrackAdsImpression(CampaignType.Interstitial, impressionInfo);
   }
}
```
Click:    
Implement ```MaxSdkCallbacks.OnInterstitialClickedEvent``` callback to track click to Magify:
```C#
MagifyManager.TrackAdsClickFor(CampaignType.Interstitial);
```

Impression fail:    
In case interstitial was not loaded - track fail reason using:     
```C#
MagifyManager.TrackImpressionFailFor(CampaignType.Interstitial, "No internet");
```
    
### Banner 
If magify returned banner campaign, you should create banner view and show in on the screen.
```C#
Magify.ICampaign campaign = MagifyManager.CampaignFor("bannerDefault");
if (campaign is Banner)
{
    /* Create and show banner view */
    return;
}
```
**Analytics**  
Track banner ads impressions, click and impression fail in a similar way as it is done for interstitials.

### Rewarded Video
Rewarded video campaign is similar to inapp campaign.
If reward campaign has `Custom` screen type you should show your own custom screen.
When showing screen you should configure it with specified rewarded products list.
Products list contains instances of  ```RewardProduct```.  
```RewardProduct``` has productId, count and context.
- ProductID: identifier of rewarded product,  
- Count: number of videos required to get a reward.
- Context: dictionary of custom attributes, that can be used for customization of product display on screen.

```C#
Magify.ICampaign campaign = MagifyManager.CampaignFor("freeDailyArtRewarded");
if (campaign is RewardedVideoCampaign)
{
    // Show custom screen
    RewardedVideoCampaign rewardedCampaign = (RewardedVideoCampaign)campaign;
    List<RewardProduct> products = rewardedCampaign.Products;
    if (rewardedCampaign.Screen.Type == ScreenType.Custom)
    {
        string screenId = rewardedCampaign.Screen.ScreenId;
        ShowCustomScreen(screenId, products);
    }
    return;
}

private void ShowCustomScreen(string screenId, List<RewardProduct> products)
{
    // Show screen code
    // Track campaign impression
}
```

If campaign has `NoUI` screen type you should request rewarded video without any UI, as if user clicked product from UI creative.
```C#
RewardedVideoCampaign rewardedCampaign = (RewardedVideoCampaign)campaign;
List<RewardProduct> products = rewardedCampaign.Products;
if (rewardedCampaign.Screen.Type == ScreenType.NoUI)
{
    // Track campaign impression and product click
    MagifyManager.TrackImpression(rewardedCampaign.Type);
    var productId = rewardedCampaign.Products[0].ProductID;
    MagifyManager.TrackProductClickFor(campaign.Type, productId);
    
    // Request rewarded video
}
```

If rewarded campaign has `Creative` screen type you should show it using ```PresentCampaign``` method.  
Method accepts 6 parameters:
- **campaign** that should be shown,
- **timeout** - time for creative image to load. If it won't be loaded before that time, creative won't be shown,
- **closeAfterClick** - close presenter after first click ot not
- **showCallback** - called right before creative show animation. Can be null
- **clickCallback** - called when the player clicks on the creative. You should start requesting reward video there. Can be null
- **closeCallback** - result of user interaction with creative. Callback parameter will be `true` if the player closed the creative by clicking the `close` button. Otherwise, it will be `false`. Can be null
- **failCallback** - called in case of any error. Can be null
If user clicked creative you should start requesting video process. Callback parameter is the error message with reason
```C#
MagifyManager.PresentCampaign(campaign, 3, true,
() => // showCallback
{
    Debug.Log("[MagifyTest]: PresentCampaign has been shown");
}
(itemIndex) => // clickCallback
{
    Debug.Log("PresentCampaign clicked");
    
    // Request rewarded video campaign
    MagifyManager.ClosePresentedCreative();
}, 
byUser => // closeCallback
{
    Debug.Log($"PresentCampaign closed. ByUser: {byUser}");
}, 
reason => // failCallback
{
    Debug.LogError($"Failed to show {nameof(RewardedVideoCampaign)} - {campaign.Name}");
    Debug.LogError($"PresentRewardedVideoCampaign failed with reason - {reason}");
});
```

You can close creative screen manually or calling method ```ClosePresentedCreative```. In this case closeCallback will be called with `false` parameter.

**Analytics**  
 When custom screen is shown or NoUI creative, you should track impression:
 ```C#
 MagifyManager.TrackImpression(campaign.Type);
 ```    
 When button was clicked from custom screen or NoUI creative you should track click and pass productId that was selected (for NoUI creative just take first item from products list):
 ```C#
 MagifyManager.TrackProductClickFor(campaign.Type, productId);
 ```    
 Ads analytics should be implemented in similar way as for interstitials
 
 ***MoPub***  
Impression:

 Implement Impression-Level Revenue Data.  
 Integration guide: https://developers.mopub.com/publishers/unity/impression-data/  
 In ```OnImpressionTrackedEvent(string adUnitId, MoPub.ImpressionData impressionData)``` method track impression to Magify.  
 Convert impressionData to ```Dictionary<string, object>``` and pass to tracking method:
 ```C#
private void OnImpressionTrackedEvent(string adUnitId, MoPub.ImpressionData impressionData)
{
   if (adUnitId == /* your application rewarded video unitId */)
   {
        string data = impressionData.JsonRepresentation;
        var impressionInfo = Magify.MiniJSON.Json.Deserialize(data) as Dictionary<string, object>;
        MagifyManager.TrackAdsImpression(CampaignType.RewardedVideo, impressionInfo);
   }
}
```
Click:

To track rewarded click implement MoPub callback  ```OnRewardedVideoClickedEvent (string adUnitId)```  and track click to Magify with selected produtId:  
```C#
MagifyManager.TrackAdsProductClickFor(CampaignType.RewardedVideo, productId);
```

***Applovin Max***  
Impression:

Implement ```MaxSdkCallbacks.OnRewardedAdDisplayedEvent``` callback track impression to Magify
```C#
private void OnRewardedAdDisplayedEvent(string adUnitId, string adNetworkName)
{
   if (adUnitId == /* your application rewarded unitId */)
   {            
       var impressionInfo = new Dictionary<string, string>()
       {
           { "tracker_type", "applovin" },
           { "adUnitId", adUnitId },
           { "networkName", adNetworkName }
       };
       MagifyManager.TrackAdsImpression(CampaignType.RewardedVideo, impressionInfo);
   }
}
```
Click:    
Implement ```MaxSdkCallbacks.OnRewardedAdClickedEvent``` callback to track click to Magify:
```C#
MagifyManager.TrackAdsProductClickFor(CampaignType.RewardedVideo, productId);
```

Impression fail:    
In case rewarded video was not loaded - track fail reason using:     
```C#
MagifyManager.TrackImpressionFailFor(CampaignType.RewardedVideo, "No internet");
```

 When rewarded bonuses added, track result event with productId:
 ```C#
 MagifyManager.TrackRewardGranted(productId);
```

### Notification
If magify returned notification campaign, you should show it using ```PresentCampaign``` method.
Method accepts 6 parameters:
- **campaign** that should be shown,
- **timeout** - time for creative to load. If it won't be loaded before that time, creative won't be shown,
- **closeAfterClick** - close presenter after first click ot not
- **showCallback** - called right before creative show animation. Can be null
- **clickCallback** - called when the player clicks on the creative. You can use it to close presented creative. Can be null
- **closeCallback** - result of user interaction with creative. Callback parameter will be `true` if the player closed the creative by clicking the `close` button. Otherwise, it will be `false`. Can be null
- **failCallback** - called in case of any error. Can be null
```C#
MagifyManager.PresentCampaign(campaign, 3.0, true,
() => // showCallback
{
    Debug.Log("[MagifyTest]: PresentCampaign has been shown");
}
(itemIndex) => // clickCallback
{
    MagifyManager.ClosePresentedCreative();
}, null, null);
```

### Bonus
Bonus campaign is similar to reward or inapp campaign just can be used to distribute free offers.
Bonus products contains instances of ```BonusProduct```.  
```BonusProduct``` has productId and context.
- ProductID: identifier of product,  
- Context: dictionary of custom attributes, that can be used for customization of product display on screen.

If bonus campaign has `Custom` screen type you should show your own custom screen.
When showing screen, configure it with specified products list.

```C#
Magify.ICampaign campaign = MagifyManager.CampaignFor("sessionStart");
if (campaign is BonusCampaign)
{
    // Show bonus screen
    BonusCampaign bonusCampaign = (BonusCampaign)campaign;
    List<BonusProduct> products = bonusCampaign.Products;
    if (bonusCampaign.Screen.Type == ScreenType.Custom)
    {
        string screenId = bonusCampaign.Screen.ScreenId;
        ShowCustomBonusScreen(screenId, products);
    }
    return;
}

private void ShowCustomBonusScreen(string screenId, List<BonusProduct> products)
{
    /* Show bonus screen */
}
```

If campaign has `NoUI` screen type you should start bonuses issuance without any UI, as if user clicked product from UI creative.
```C#
BonusCampaign bonusCampaign = (BonusCampaign)campaign;
List<BonusProduct> products = bonusCampaign.Products;
if (bonusCampaign.Screen.Type == ScreenType.NoUI)
{
    // Track campaign impression and product click
    // Add bonuses
}
```

If bonus campaign has `Creative` screen type you should show it using ```PresentCampaign``` method, similar to other campaigns.
Method accepts 6 parameters:
- **campaign** that should be shown,
- **timeout** - time for creative image to load. If it won't be loaded before that time, creative won't be shown,
- **closeAfterClick** - close presenter after first click ot not
- **showCallback** - called right before creative show animation. Can be null
- **clickCallback** - called when the player clicks on the creative. You should react with adding bonuses here. Can be null
- **closeCallback** - result of user interaction with bonus creative. Callback parameter will be `true` if the player closed the creative by clicking the `close` button. Otherwise, it will be `false`. Can be null
- **failCallback** - called in case of any error. Can be null
If user clicked creative you should start bonus adding flow. Callback parameter is the error message with reason
```C#
MagifyManager.PresentCampaign(campaign, 3, true,
() => // showCallback
{
    Debug.Log("[MagifyTest]: PresentCampaign has been shown");
}
(itemIndex) => // clickCallback
{
    Debug.Log("PresentCampaign clicked");

    // add bonuses to user here
    MagifyManager.TrackFreeBonusGranted(productId);
    MagifyManager.ClosePresentedCreative();
}, 
byUser => // closeCallback
{
    Debug.Log($"PresentCampaign closed. ByUser: {byUser}");
}, 
reason => // failCallback
{
    Debug.LogError($"Failed to show {nameof(BonusCampaign)} - {campaign.Name}");
    Debug.LogError($"PresentCampaign failed with reason - {reason}");
});
```

You can close bonus creative screen manually or calling method ```ClosePresentedCreative```. In this case closeCallback will be called with `false` parameter.

**Analytics**  
 When custom screen is shown or NoUI creative, you should track impression:
 ```C#
 MagifyManager.TrackImpression(campaign.Type);
 ```    
 When button clicked from custom screen or NoUI creative you should track click and pass productId that was selected (for NoUI creative just take first item from products list):
 ```C#
 MagifyManager.TrackProductClickFor(campaign.Type, productId);
 ```    
 When bonuses added, you should track bonus grant event with productId:
 ```C#
 MagifyManager.TrackFreeBonusGranted(productId);
```   

### Mixed
Mixed campaign can be used to distribute products of different type: inapps, subscriptions, rewards or free bonuses.
Mixed products list contains instances of base ```ICampaignProduct```.  
```ICampaignProduct``` can be casted to one of the product types and handled accordingly.

Most often, mixed campaigns are distributed with `Custom` screen creatives.
When showing screen, configure it with specified products list and handle products

```C#
Magify.ICampaign campaign = MagifyManager.CampaignFor("sessionStart");
if (campaign is MixedCampaign)
{
    // Show mixed campaign screen
    MixedCampaign mixedCampaign = (MixedCampaign)campaign;
    List<ICampaignProduct> products = mixedCampaign.Products;
    if (mixedCampaign.Screen.Type == ScreenType.Custom)
    {
        string screenId = mixedCampaign.Screen.ScreenId;
        ShowMixedScreen(screenId, products);
    }
    return;
}

private void ShowMixedScreen(string screenId, List<ICampaignProduct> products)
{
    /* Show mixed screen */
}
```

For other screen types everything should be handled similarly to other campaigns like inapps or rewards.


**Analytics**  
 When custom screen is shown or NoUI creative, you should track impression:
 ```C#
 MagifyManager.TrackImpression(campaign.Type);
 ```    
 When button clicked from custom screen or NoUI creative you should track click and pass productId that was selected (for NoUI creative just take first item from products list):
 ```C#
 MagifyManager.TrackProductClickFor(campaign.Type, productId);
 ```    
 Handling products should be similar to how products of this type are processed:
 For example for inapp products, call ```MagifyManager.TrackInAppFor``` after product purchase, call ```MagifyManager.TrackRewardGranted``` for reward resuls, ```MagifyManager.TrackFreeBonusGranted``` for bonus result etc.

## Presenters

The presenter is a tool for displaying creatives for campaigns that implement the `ICampaignWithCreative` interface (`InAppCampaign`, `RewardedVideoCampaign`, `PromoCampaign`, and others). For any campany you have to call one method:
```C#
MagifyManager.PresentCampaign(...);
```
Currently SDK supports 3 types of presenters - `Fullscreen`, `Popover` and `Bundle`. There are also default implementations built into the SDK itself. In addition, it is possible to customize them for each project using methods methods `SetCustomFullscreenPresenter`, `SetCustomPopoverPresenter` and `SetCustomBundlePresenter`. To do this at the project level you have 2 options:
- Create a prefab with the `TexturePresenter` (for Fullscreen and Image creatives) or `BundlePresenter` (for Bundle creative) component and pass reference of it to the SDK.
- Make your own implementation of the presenter based on the `IPresenter<Texture>` or `IPresenter<AssetBundle>` interface and pass it to the SDK.

## Postprocessor

For correct applications promotions in iOS builds you should add ulr-schemes of promoted app to project Info.plist file.
To do this add the following script to Scripts/Editor of your project:
```C#
public static class iOSPostProcessBuild
{
    [PostProcessBuild]
    public static void UpdateiOSProjectInfoPlist(BuildTarget target, string pathToBuildProject)
    {
        const string urlSchemesKey = "LSApplicationQueriesSchemes";
        string[] appSchemes = {
            // !!! Replace with the list of actual application url-schemes
            "fb2211690755750421", "fb353210168689481" 
        };
        if (target == BuildTarget.iOS)
        {
            string plistPath = pathToBuildProject + "/Info.plist";
            PlistDocument plist = new PlistDocument();
            plist.ReadFromFile(plistPath);
            PlistElementDict root = plist.root;
            PlistElementArray schemes = new PlistElementArray();
            foreach (string schema in appSchemes)
            {
                schemes.AddString(schema);
            }
            root[urlSchemesKey] = schemes;
            plist.WriteToFile(plistPath);
        }
    }
}
```
