crowdhub-plugin-flurryanalytics
This plugin provides an interface for Capacitor apps to use the Flurry Analytics API
Install
Capacitor 5:
npm install crowdhub-plugin-flurryanalytics@latest
npx cap sync
Capacitor 4:
npm install crowdhub-plugin-flurryanalytics@4
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
Log this event when the App is activated
Returns: Promise<{ value: string; }>
logApplicationSubmitted()
logApplicationSubmitted() => Promise<{ value: string; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
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; }>
Log this event when user skips the tutorial
Param | Type |
---|---|
options |
{ stepNumber: number; tutorialName?: string; } |
Returns: Promise<{ value: string; }>
logPrivacyPromptDisplayed()
logPrivacyPromptDisplayed() => Promise<{ value: string; }>
Log this event when a privacy prompt is displayed
Returns: Promise<{ value: string; }>
logPrivacyOptIn()
logPrivacyOptIn() => Promise<{ value: string; }>
Log this event when a user opts in (on the privacy prompt)
Returns: Promise<{ value: string; }>
logPrivacyOptOut()
logPrivacyOptOut() => Promise<{ value: string; }>
Log this event when a user opts out (on the privacy prompt)
Returns: Promise<{ value: string; }>
░░░░▒▒░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░▒▒░░ ░░░░▒▒░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░▒▒░░ ░░▒▒▒▒▒▒░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░▒▒▒▒▒▒ ░░▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒ ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░████░░░░░░░░████░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░ ░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░████░░░░░░░░████░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░ ░░░░░░▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒░░░░ ░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░ ░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░ ░░░░░░░░▒▒▒▒▒▒░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░▒▒▒▒▒▒░░░░░░ ░░░░░░░░▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒▒▒▒░░░░░░ ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░ ░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░ ░░░░░░▒▒▒▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒░░░░ ░░░░▒▒▒▒░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░▒▒▒▒░░ ░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░ ░░░░▒▒▒▒░░░░░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒░░░░░░▒▒▒▒░░ ░░░░░░▒▒░░░░░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒░░░░░░▒▒░░░░ ░░░░░░▒▒░░░░░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒░░░░░░▒▒░░░░ ░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░