crowdhub-plugin-flurryanalytics
This plugin provides an interface for Capacitor apps to use the Flurry Analytics API
Install
npm install crowdhub-plugin-flurryanalytics
npx cap sync
Implementation
Web
In order to initialize a session, you simply need to call once within your app’s session, so you’d likely want to include this call on a deviceready event.
import { FlurryAnalytics } from 'crowdhub-plugin-flurryanalytics';
FlurryAnalytics.initialize({
apiKey: 'YOUR-API-KEY-HERE',
});
Afterwards you can safely call any of the provided methods from the FlurryAnalytics class!
For demographics methods (setAge, setGender, setUserId), these must be called prior to initializing a Flurry session.
iOS
Android
As with many community created Capacitor plugins, you may need to manually register this plugin in your MainActivity.java like so:
package your.app.bundle;
import android.os.Bundle;
import com.crowdhubapps.plugin.flurryanalytics.FlurryAnalyticsPlugin;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
registerPlugin(FlurryAnalyticsPlugin.class);
super.onCreate(savedInstanceState);
}
}
If you get an error related to finding the package, you may also need to Right-Click the crowdhub-flurry-analytics folder in Android Studio and select the ‘Convert Java File to Kotlin File’ option. This will prompt you to configure Kotlin in the project, which is what the plugin is written in. You only need to configure Single module: android.crowdhubapps-plugin-flurryanalytics.
API
initialize(...)logContentRated(...)logContentViewed(...)logContentSaved(...)logProductCustomized()logSubscriptionStarted(...)logSubscriptionEnded(...)logGroupJoined(...)logGroupLeft(...)logLogin(...)logLogout(...)logUserRegistered(...)logSearchResultViewed(...)logKeywordSearched(...)logLocationSearched(...)logInvite(...)logShare(...)logLike(...)logComment(...)logMediaCaptured(...)logMediaStarted(...)logMediaStopped(...)logMediaPaused(...)logCustomEvent(...)endTimedEvent(...)setUserId(...)setAge(...)setGender(...)logError(...)logAdClick(...)logAdImpression(...)logAdRewarded(...)logAdSkipped(...)logCreditsSpent(...)logCreditsPurchased(...)logCreditsEarned(...)logAchievementUnlocked(...)logLevelCompleted(...)logLevelFailed(...)logLevelUp(...)logLevelStarted(...)logLevelSkip(...)logScorePosted(...)logAppActivated()logApplicationSubmitted()logAddItemToCart(...)logAddItemToWishList(...)logCompletedCheckout(...)logPaymentInfoAdded(...)logItemViewed(...)logItemListViewed(...)logPurchased(...)logPurchaseRefunded(...)logRemoveItemFromCart(...)logCheckoutInitiated(...)logFundsDonated(...)logUserScheduled()logOfferPresented(...)logTutorialStarted(...)logTutorialCompleted(...)logTutorialStepCompleted(...)logTutorialSkipped(...)logPrivacyPromptDisplayed()logPrivacyOptIn()logPrivacyOptOut()
initialize(…)
initialize(options: { apiKey: string; logLevel?: 'verbose' | 'debug' | 'info' | 'warn' | 'error'; crashReportingEnabled?: boolean; appVersion?: string; iapReportingEnabled?: boolean; }) => Promise<{ value: string; }>
Initialize Flurry once within the session by passing through an API key
| Param | Type |
|---|---|
options |
{ apiKey: string; logLevel?: 'error' | 'warn' | 'info' | 'verbose' | 'debug'; crashReportingEnabled?: boolean; appVersion?: string; iapReportingEnabled?: boolean; } |
Returns: Promise<{ value: string; }>
logContentRated(…)
logContentRated(options: { contentId: string; contentRating: string; contentName?: string; contentType?: string; }) => Promise<{ value: string; }>
Log this event when a user rates a content in the App
| Param | Type |
|---|---|
options |
{ contentId: string; contentRating: string; contentName?: string; contentType?: string; } |
Returns: Promise<{ value: string; }>
logContentViewed(…)
logContentViewed(options: { contentId: string; contentName?: string; contentType?: string; }) => Promise<{ value: string; }>
Log this event when a specific content is viewed by a user
| Param | Type |
|---|---|
options |
{ contentId: string; contentName?: string; contentType?: string; } |
Returns: Promise<{ value: string; }>
logContentSaved(…)
logContentSaved(options: { contentId: string; contentName?: string; contentType?: string; }) => Promise<{ value: string; }>
Log this event when a user saves the content in the App
| Param | Type |
|---|---|
options |
{ contentId: string; contentName?: string; contentType?: string; } |
Returns: Promise<{ value: string; }>
logProductCustomized()
logProductCustomized() => Promise<{ value: string; }>
Log this event when a user customizes the App/product
Returns: Promise<{ value: string; }>
logSubscriptionStarted(…)
logSubscriptionStarted(options: { price: number; isAnnualSubscription: boolean; trialDays?: number; predictedLTV?: string; currencyType?: string; subscriptionCountry?: string; }) => Promise<{ value: string; }>
Log this event at the start of a paid subscription for a service or product
| Param | Type |
|---|---|
options |
{ price: number; isAnnualSubscription: boolean; trialDays?: number; predictedLTV?: string; currencyType?: string; subscriptionCountry?: string; } |
Returns: Promise<{ value: string; }>
logSubscriptionEnded(…)
logSubscriptionEnded(options: { isAnnualSubscription: boolean; currencyType?: string; subscriptionCountry?: string; }) => Promise<{ value: string; }>
Log this event when a user unsubscribes from a paid subscription for a service or product
| Param | Type |
|---|---|
options |
{ isAnnualSubscription: boolean; currencyType?: string; subscriptionCountry?: string; } |
Returns: Promise<{ value: string; }>
logGroupJoined(…)
logGroupJoined(options: { groupName?: string; }) => Promise<{ value: string; }>
Log this event when user joins a group.
| Param | Type |
|---|---|
options |
{ groupName?: string; } |
Returns: Promise<{ value: string; }>
logGroupLeft(…)
logGroupLeft(options: { groupName?: string; }) => Promise<{ value: string; }>
Log this event when user leaves a group
| Param | Type |
|---|---|
options |
{ groupName?: string; } |
Returns: Promise<{ value: string; }>
logLogin(…)
logLogin(options: { userId?: string; method?: string; }) => Promise<{ value: string; }>
Log this event when a user login on the App
| Param | Type |
|---|---|
options |
{ userId?: string; method?: string; } |
Returns: Promise<{ value: string; }>
logLogout(…)
logLogout(options: { userId?: string; method?: string; }) => Promise<{ value: string; }>
Log this event when a user logout of the App
| Param | Type |
|---|---|
options |
{ userId?: string; method?: string; } |
Returns: Promise<{ value: string; }>
logUserRegistered(…)
logUserRegistered(options: { userId?: string; method?: string; }) => Promise<{ value: string; }>
Log the event when a user registers (signup). Helps capture the method used to sign-up (signup with google/apple or email address)
| Param | Type |
|---|---|
options |
{ userId?: string; method?: string; } |
Returns: Promise<{ value: string; }>
logSearchResultViewed(…)
logSearchResultViewed(options: { query?: string; searchType?: string; }) => Promise<{ value: string; }>
Log this event when user views search results
| Param | Type |
|---|---|
options |
{ query?: string; searchType?: string; } |
Returns: Promise<{ value: string; }>
logKeywordSearched(…)
logKeywordSearched(options: { query?: string; searchType?: string; }) => Promise<{ value: string; }>
Log this event when a user searches for a keyword using Search
| Param | Type |
|---|---|
options |
{ query?: string; searchType?: string; } |
Returns: Promise<{ value: string; }>
logLocationSearched(…)
logLocationSearched(options: { query?: string; }) => Promise<{ value: string; }>
Log this event when a user searches for a location using Search
| Param | Type |
|---|---|
options |
{ query?: string; } |
Returns: Promise<{ value: string; }>
logInvite(…)
logInvite(options: { userId?: string; method?: string; }) => Promise<{ value: string; }>
Log this event when a user invites another user
| Param | Type |
|---|---|
options |
{ userId?: string; method?: string; } |
Returns: Promise<{ value: string; }>
logShare(…)
logShare(options: { socialContentId: string; socialContentName?: string; method?: string; }) => Promise<{ value: string; }>
Log this event when a user shares content with another user in the App
| Param | Type |
|---|---|
options |
{ socialContentId: string; socialContentName?: string; method?: string; } |
Returns: Promise<{ value: string; }>
logLike(…)
logLike(options: { socialContentId: string; socialContentName?: string; likeType?: string; }) => Promise<{ value: string; }>
Log this event when a user likes a social content. e.g. likeType captures what kind of like is logged (“celebrate”, “insightful”, etc)
| Param | Type |
|---|---|
options |
{ socialContentId: string; socialContentName?: string; likeType?: string; } |
Returns: Promise<{ value: string; }>
logComment(…)
logComment(options: { socialContentId: string; socialContentName?: string; }) => Promise<{ value: string; }>
Log this event when a user comments or replies on a social post
| Param | Type |
|---|---|
options |
{ socialContentId: string; socialContentName?: string; } |
Returns: Promise<{ value: string; }>
logMediaCaptured(…)
logMediaCaptured(options: { mediaId?: string; mediaName?: string; mediaType?: string; }) => Promise<{ value: string; }>
Log this event when an image, audio or a video is captured
| Param | Type |
|---|---|
options |
{ mediaId?: string; mediaName?: string; mediaType?: string; } |
Returns: Promise<{ value: string; }>
logMediaStarted(…)
logMediaStarted(options: { mediaId?: string; mediaName?: string; mediaType?: string; }) => Promise<{ value: string; }>
Log this event when an audio or video starts
| Param | Type |
|---|---|
options |
{ mediaId?: string; mediaName?: string; mediaType?: string; } |
Returns: Promise<{ value: string; }>
logMediaStopped(…)
logMediaStopped(options: { duration: number; mediaId?: string; mediaName?: string; mediaType?: string; }) => Promise<{ value: string; }>
Log this event when an audio or video is stopped
| Param | Type |
|---|---|
options |
{ duration: number; mediaId?: string; mediaName?: string; mediaType?: string; } |
Returns: Promise<{ value: string; }>
logMediaPaused(…)
logMediaPaused(options: { duration: number; mediaId?: string; mediaName?: string; mediaType?: string; }) => Promise<{ value: string; }>
Log this event when an audio or video is paused
| Param | Type |
|---|---|
options |
{ duration: number; mediaId?: string; mediaName?: string; mediaType?: string; } |
Returns: Promise<{ value: string; }>
logCustomEvent(…)
logCustomEvent(options: { eventName: string; eventParams?: { [key: string]: string; } | undefined; eventTimed?: boolean | undefined; }) => Promise<{ value: string; }>
Log a custom event in the app. You may provide up to ten additional parameters as key/value pairs, both of which must be strings You may also enable this event to be timed, calling endTimedEvent to terminate its logging
| Param | Type |
|---|---|
options |
{ eventName: string; eventParams?: { [key: string]: string; }; eventTimed?: boolean; } |
Returns: Promise<{ value: string; }>
endTimedEvent(…)
endTimedEvent(options: { eventName: string; }) => Promise<{ value: string; }>
Not yet implemented
| Param | Type |
|---|---|
options |
{ eventName: string; } |
Returns: Promise<{ value: string; }>
setUserId(…)
setUserId(options: { userId: string; }) => Promise<{ value: string; }>
After identifying the user, use this to log the user’s assigned ID or username in your system. You must call this function prior to starting the Flurry session
| Param | Type |
|---|---|
options |
{ userId: string; } |
Returns: Promise<{ value: string; }>
setAge(…)
setAge(options: { userAge: number; }) => Promise<{ value: string; }>
After identifying the user, use this to log the user’s age. Valid inputs are between 1 and 109. You must call this function prior to starting the Flurry session
| Param | Type |
|---|---|
options |
{ userAge: number; } |
Returns: Promise<{ value: string; }>
setGender(…)
setGender(options: { userGender: 'm' | 'f'; }) => Promise<{ value: string; }>
After identifying the user, use this to log the user’s gender. Valid inputs are m (male) or f (female). You must call this function prior to starting the Flurry session
| Param | Type |
|---|---|
options |
{ userGender: 'm' | 'f'; } |
Returns: Promise<{ value: string; }>
logError(…)
logError(options: { errorId?: string; errorMessage?: string; error?: string; }) => Promise<{ value: string; }>
Use this to log exceptions and/or errors that occur in your app. Flurry will report the first 10 errors that occur in each session.
| Param | Type |
|---|---|
options |
{ errorId?: string; errorMessage?: string; error?: string; } |
Returns: Promise<{ value: string; }>
logAdClick(…)
logAdClick(options: { adType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user clicks on an Ad
| Param | Type |
|---|---|
options |
{ adType?: string; } |
Returns: Promise<{ value: string; }>
logAdImpression(…)
logAdImpression(options: { adType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user views an Ad impression
| Param | Type |
|---|---|
options |
{ adType?: string; } |
Returns: Promise<{ value: string; }>
logAdRewarded(…)
logAdRewarded(options: { adType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user is granted a reward for viewing a rewarded Ad
| Param | Type |
|---|---|
options |
{ adType?: string; } |
Returns: Promise<{ value: string; }>
logAdSkipped(…)
logAdSkipped(options: { adType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user skips an Ad
| Param | Type |
|---|---|
options |
{ adType?: string; } |
Returns: Promise<{ value: string; }>
logCreditsSpent(…)
logCreditsSpent(options: { levelNumber?: number; totalAmount: number; isCurrencySoft?: boolean; creditType?: string; creditId?: string; creditName?: string; currencyType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user spends credit in the app
| Param | Type |
|---|---|
options |
{ levelNumber?: number; totalAmount: number; isCurrencySoft?: boolean; creditType?: string; creditId?: string; creditName?: string; currencyType?: string; } |
Returns: Promise<{ value: string; }>
logCreditsPurchased(…)
logCreditsPurchased(options: { levelNumber?: number; totalAmount: number; isCurrencySoft?: boolean; creditType?: string; creditId?: string; creditName?: string; currencyType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user purchases credit in the app
| Param | Type |
|---|---|
options |
{ levelNumber?: number; totalAmount: number; isCurrencySoft?: boolean; creditType?: string; creditId?: string; creditName?: string; currencyType?: string; } |
Returns: Promise<{ value: string; }>
logCreditsEarned(…)
logCreditsEarned(options: { levelNumber?: number; totalAmount: number; isCurrencySoft?: boolean; creditType?: string; creditId?: string; creditName?: string; currencyType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user earns credit in the app
| Param | Type |
|---|---|
options |
{ levelNumber?: number; totalAmount: number; isCurrencySoft?: boolean; creditType?: string; creditId?: string; creditName?: string; currencyType?: string; } |
Returns: Promise<{ value: string; }>
logAchievementUnlocked(…)
logAchievementUnlocked(options: { achievementId?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user unlocks an achievement in the app
| Param | Type |
|---|---|
options |
{ achievementId?: string; } |
Returns: Promise<{ value: string; }>
logLevelCompleted(…)
logLevelCompleted(options: { levelNumber: number; levelName?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when an App user completes a level
| Param | Type |
|---|---|
options |
{ levelNumber: number; levelName?: string; } |
Returns: Promise<{ value: string; }>
logLevelFailed(…)
logLevelFailed(options: { levelNumber: number; levelName?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when an App user fails a level
| Param | Type |
|---|---|
options |
{ levelNumber: number; levelName?: string; } |
Returns: Promise<{ value: string; }>
logLevelUp(…)
logLevelUp(options: { levelNumber: number; levelName?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when an App user levels up
| Param | Type |
|---|---|
options |
{ levelNumber: number; levelName?: string; } |
Returns: Promise<{ value: string; }>
logLevelStarted(…)
logLevelStarted(options: { levelNumber: number; levelName?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when an App user starts a level
| Param | Type |
|---|---|
options |
{ levelNumber: number; levelName?: string; } |
Returns: Promise<{ value: string; }>
logLevelSkip(…)
logLevelSkip(options: { levelNumber: number; levelName?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when an App user skips a level
| Param | Type |
|---|---|
options |
{ levelNumber: number; levelName?: string; } |
Returns: Promise<{ value: string; }>
logScorePosted(…)
logScorePosted(options: { levelNumber?: number; score: number; }) => Promise<{ value: string; }>
Not yet implemented Log this event when an App user posts his score
| Param | Type |
|---|---|
options |
{ levelNumber?: number; score: number; } |
Returns: Promise<{ value: string; }>
logAppActivated()
logAppActivated() => Promise<{ value: string; }>
Not yet implemented Log this event when the App is activated
Returns: Promise<{ value: string; }>
logApplicationSubmitted()
logApplicationSubmitted() => Promise<{ value: string; }>
Not yet implemented Log this event when a user submits an application through the App
Returns: Promise<{ value: string; }>
logAddItemToCart(…)
logAddItemToCart(options: { itemCount: number; price: number; itemId?: string; itemName?: string; itemType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when an item is added to the cart
| Param | Type |
|---|---|
options |
{ itemCount: number; price: number; itemId?: string; itemName?: string; itemType?: string; } |
Returns: Promise<{ value: string; }>
logAddItemToWishList(…)
logAddItemToWishList(options: { itemCount: number; price: number; itemId?: string; itemName?: string; itemType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when an item is added to the wish list
| Param | Type |
|---|---|
options |
{ itemCount: number; price: number; itemId?: string; itemName?: string; itemType?: string; } |
Returns: Promise<{ value: string; }>
logCompletedCheckout(…)
logCompletedCheckout(options: { itemCount: number; totalAmount: number; currencyType?: string; transactionId?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when checkout is completed or transaction is successfully completed
| Param | Type |
|---|---|
options |
{ itemCount: number; totalAmount: number; currencyType?: string; transactionId?: string; } |
Returns: Promise<{ value: string; }>
logPaymentInfoAdded(…)
logPaymentInfoAdded(options: { success: boolean; paymentType: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when payment information is added during a checkout process
| Param | Type |
|---|---|
options |
{ success: boolean; paymentType: string; } |
Returns: Promise<{ value: string; }>
logItemViewed(…)
logItemViewed(options: { price?: number; itemId: string; itemName?: string; itemType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when an item is viewed
| Param | Type |
|---|---|
options |
{ price?: number; itemId: string; itemName?: string; itemType?: string; } |
Returns: Promise<{ value: string; }>
logItemListViewed(…)
logItemListViewed(options: { itemListType: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a list of items is viewed
| Param | Type |
|---|---|
options |
{ itemListType: string; } |
Returns: Promise<{ value: string; }>
logPurchased(…)
logPurchased(options: { totalAmount: number; success: boolean; itemCount?: number; itemId?: string; itemName?: string; itemType?: string; currencyType?: string; transactionId?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user does a purchase in the App
| Param | Type |
|---|---|
options |
{ totalAmount: number; success: boolean; itemCount?: number; itemId?: string; itemName?: string; itemType?: string; currencyType?: string; transactionId?: string; } |
Returns: Promise<{ value: string; }>
logPurchaseRefunded(…)
logPurchaseRefunded(options: { price: number; currencyType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a purchase is refunded
| Param | Type |
|---|---|
options |
{ price: number; currencyType?: string; } |
Returns: Promise<{ value: string; }>
logRemoveItemFromCart(…)
logRemoveItemFromCart(options: { price?: number; itemId: string; itemName?: string; itemType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user removes an item from the cart
| Param | Type |
|---|---|
options |
{ price?: number; itemId: string; itemName?: string; itemType?: string; } |
Returns: Promise<{ value: string; }>
logCheckoutInitiated(…)
logCheckoutInitiated(options: { itemCount: number; totalAmount: number; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user starts checkout
| Param | Type |
|---|---|
options |
{ itemCount: number; totalAmount: number; } |
Returns: Promise<{ value: string; }>
logFundsDonated(…)
logFundsDonated(options: { price: number; currencyType?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user donates fund to your App or through the App
| Param | Type |
|---|---|
options |
{ price: number; currencyType?: string; } |
Returns: Promise<{ value: string; }>
logUserScheduled()
logUserScheduled() => Promise<{ value: string; }>
Not yet implemented Log this event when user schedules an appointment using the App
Returns: Promise<{ value: string; }>
logOfferPresented(…)
logOfferPresented(options: { price: number; itemId: string; itemName?: string; itemCategory?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when an offer is presented to the user
| Param | Type |
|---|---|
options |
{ price: number; itemId: string; itemName?: string; itemCategory?: string; } |
Returns: Promise<{ value: string; }>
logTutorialStarted(…)
logTutorialStarted(options: { tutorialName?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user starts a tutorial
| Param | Type |
|---|---|
options |
{ tutorialName?: string; } |
Returns: Promise<{ value: string; }>
logTutorialCompleted(…)
logTutorialCompleted(options: { tutorialName?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a user completes a tutorial
| Param | Type |
|---|---|
options |
{ tutorialName?: string; } |
Returns: Promise<{ value: string; }>
logTutorialStepCompleted(…)
logTutorialStepCompleted(options: { stepNumber: number; tutorialName?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when a specific tutorial step is completed
| Param | Type |
|---|---|
options |
{ stepNumber: number; tutorialName?: string; } |
Returns: Promise<{ value: string; }>
logTutorialSkipped(…)
logTutorialSkipped(options: { stepNumber: number; tutorialName?: string; }) => Promise<{ value: string; }>
Not yet implemented Log this event when user skips the tutorial
| Param | Type |
|---|---|
options |
{ stepNumber: number; tutorialName?: string; } |
Returns: Promise<{ value: string; }>
logPrivacyPromptDisplayed()
logPrivacyPromptDisplayed() => Promise<{ value: string; }>
Not yet implemented Log this event when a privacy prompt is displayed
Returns: Promise<{ value: string; }>
logPrivacyOptIn()
logPrivacyOptIn() => Promise<{ value: string; }>
Not yet implemented Log this event when a user opts in (on the privacy prompt)
Returns: Promise<{ value: string; }>
logPrivacyOptOut()
logPrivacyOptOut() => Promise<{ value: string; }>
Not yet implemented Log this event when a user opts out (on the privacy prompt)
Returns: Promise<{ value: string; }>
░░░░▒▒░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░▒▒░░ ░░░░▒▒░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░▒▒░░ ░░▒▒▒▒▒▒░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░▒▒▒▒▒▒ ░░▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒ ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░████░░░░░░░░████░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░ ░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░████░░░░░░░░████░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░ ░░░░░░▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒░░░░ ░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░ ░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░ ░░░░░░░░▒▒▒▒▒▒░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░▒▒▒▒▒▒░░░░░░ ░░░░░░░░▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒▒▒▒░░░░░░ ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░ ░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░ ░░░░░░▒▒▒▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒░░░░ ░░░░▒▒▒▒░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░▒▒▒▒░░ ░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░ ░░░░▒▒▒▒░░░░░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒░░░░░░▒▒▒▒░░ ░░░░░░▒▒░░░░░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒░░░░░░▒▒░░░░ ░░░░░░▒▒░░░░░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒░░░░░░▒▒░░░░ ░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░