A Capacitor plugin that provides a TypeScript interface for Flurry Analytics API

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(…)

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; }>


░░░░▒▒░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░▒▒░░ ░░░░▒▒░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░▒▒░░ ░░▒▒▒▒▒▒░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░▒▒▒▒▒▒ ░░▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒ ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░████░░░░░░░░████░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░ ░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░████░░░░░░░░████░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░ ░░░░░░▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒░░░░ ░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░ ░░░░░░░░▒▒▒▒▒▒░░░░░░░░░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░ ░░░░░░░░▒▒▒▒▒▒░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░▒▒▒▒▒▒░░░░░░ ░░░░░░░░▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒▒▒▒▒░░░░░░ ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░ ░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░ ░░░░░░▒▒▒▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒░░░░ ░░░░▒▒▒▒░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░▒▒▒▒░░ ░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒▒▒░░░░░░░░▒▒▒▒░░ ░░░░▒▒▒▒░░░░░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒░░░░░░▒▒▒▒░░ ░░░░░░▒▒░░░░░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒░░░░░░▒▒░░░░ ░░░░░░▒▒░░░░░░▒▒▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒▒▒░░░░░░▒▒░░░░ ░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒▒░░░░░░░░░░░░░░

GitHub

View Github