diff --git a/.gitignore b/.gitignore index 02f7d626..c21e214d 100644 --- a/.gitignore +++ b/.gitignore @@ -60,7 +60,6 @@ buck-out/ # Other Files app/google-services.json -app/src/main/assets/index.android.bundle *.log .vagrant *.hprof diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 285c216d..33fca8b4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,18 +3,12 @@ stages: - deploy - release -variables: - REACT_NATIVE_BRANCH: "master" build apk: stage: build image: lbry/android-base:platform-28 before_script: - apt-get -y update && apt-get -y install build-essential ca-certificates curl git gpg-agent openjdk-8-jdk software-properties-common wget zipalign - - wget -q https://nodejs.org/dist/latest-v10.x/node-v10.19.0-linux-x64.tar.gz && tar xf node-v10.19.0-linux-x64.tar.gz -C /opt - - ln -fs /opt/node-v10.19.0-linux-x64/bin/node /usr/bin/node - - ln -fs /opt/node-v10.19.0-linux-x64/bin/npm /usr/bin/npm - - ln -fs /opt/node-v10.19.0-linux-x64/bin/npx /usr/bin/npx - chmod u+x $CI_PROJECT_DIR/gradlew - export ANDROID_SDK_ROOT=~/.buildozer/android/platform/android-sdk-23 - export BUILD_VERSION=$($CI_PROJECT_DIR/gradlew -q printVersionName --console=plain | tail -1) @@ -26,25 +20,13 @@ build apk: script: - export PATH=/usr/bin:$PATH - echo "$PGP_PRIVATE_KEY" | gpg --batch --import - - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - - - echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list - echo "deb https://dl.bintray.com/sobolevn/deb git-secret main" | tee -a /etc/apt/sources.list - wget -O - https://api.bintray.com/users/sobolevn/keys/gpg/public.key | apt-key add - - - apt-get -y update && apt-get -y install yarn git-secret + - apt-get -y update && apt-get -y install git-secret - git secret reveal - - npm install -g react-native-cli - - cd ~/ - - git clone --single-branch --branch $REACT_NATIVE_BRANCH https://github.com/lbryio/lbry-react-native - - cd lbry-react-native - - chmod u+x bundle-android.sh - yarn - - rm -rf android # temporary, should be a submodule init? - - cp -rf $CI_PROJECT_DIR/ android/ - - cd android - chmod u+x ./release.sh - ./release.sh - - mkdir -p $CI_PROJECT_DIR/bin/ && cp bin/*.apk $CI_PROJECT_DIR/bin/ - - cd $CI_PROJECT_DIR - cp bin/browser-$BUILD_VERSION-release__arm.apk /dev/null - cp bin/browser-$BUILD_VERSION-release__arm64.apk /dev/null diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 9c973bf1..00000000 --- a/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -FROM thyrlian/android-sdk - -## Dependencies to run as root: -ENV DEBIAN_FRONTEND=noninteractive -RUN dpkg --add-architecture i386 && \ - apt-get -y update && \ - apt-get install -y \ - curl ca-certificates software-properties-common gpg-agent wget \ - python3.7 python3.7-dev python3-pip python2.7 python2.7-dev python3.7-venv \ - python-pip zlib1g-dev m4 zlib1g:i386 libc6-dev-i386 gawk nodejs npm unzip openjdk-8-jdk \ - autoconf autogen automake libtool libffi-dev build-essential \ - ccache git libncurses5:i386 libstdc++6:i386 \ - libgtk2.0-0:i386 libpangox-1.0-0:i386 libpangoxft-1.0-0:i386 libidn11:i386 -RUN npm install -g npm@latest -RUN npm install -g yarn react-native-cli && \ - pip2 install --upgrade cython setuptools && \ - pip2 install git+https://github.com/lbryio/buildozer.git@master && \ - ln -s /src/scripts/build-docker.sh /usr/local/bin/build && \ - adduser lbry-android --gecos GECOS --shell /bin/bash --disabled-password --home /home/lbry-android && \ - mkdir /home/lbry-android/.npm-packages && \ - echo "prefix=/home/lbry-android/.npm-packages" > /home/lbry-android/.npmrc && \ - chown -R lbry-android:lbry-android /home/lbry-android && \ - mkdir /src && \ - chown lbry-android:lbry-android /src && \ - mkdir /dist && \ - chown lbry-android:lbry-android /dist - -## Further setup done by lbry-android user: -USER lbry-android - -COPY scripts/docker-build.sh /home/lbry-android/bin/build -COPY scripts/docker-setup.sh /home/lbry-android/bin/setup -CMD ["/home/lbry-android/bin/build"] diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/BUCK b/app/BUCK deleted file mode 100644 index 3f7eb728..00000000 --- a/app/BUCK +++ /dev/null @@ -1,55 +0,0 @@ -# To learn about Buck see [Docs](https://buckbuild.com/). -# To run your application with Buck: -# - install Buck -# - `npm start` - to start the packager -# - `cd android` -# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` -# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck -# - `buck install -r android/app` - compile, install and run application -# - -load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") - -lib_deps = [] - -create_aar_targets(glob(["libs/*.aar"])) - -create_jar_targets(glob(["libs/*.jar"])) - -android_library( - name = "all-libs", - exported_deps = lib_deps, -) - -android_library( - name = "app-code", - srcs = glob([ - "src/main/java/**/*.java", - ]), - deps = [ - ":all-libs", - ":build_config", - ":res", - ], -) - -android_build_config( - name = "build_config", - package = "com.lbryandroid", -) - -android_resource( - name = "res", - package = "com.lbryandroid", - res = "src/main/res", -) - -android_binary( - name = "app", - keystore = "//android/keystores:debug", - manifest = "src/main/AndroidManifest.xml", - package_type = "debug", - deps = [ - ":app-code", - ], -) diff --git a/app/build.gradle b/app/build.gradle index aa3e40f4..81794584 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,137 +1,8 @@ -apply plugin: "com.android.application" - -import com.android.build.OutputFile - -/** - * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets - * and bundleReleaseJsAndAssets). - * These basically call `react-native bundle` with the correct arguments during the Android build - * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the - * bundle directly from the development server. Below you can see all the possible configurations - * and their defaults. If you decide to add a configuration block, make sure to add it before the - * `apply from: "../../node_modules/react-native/react.gradle"` line. - * - * project.ext.react = [ - * // the name of the generated asset file containing your JS bundle - * bundleAssetName: "index.android.bundle", - * - * // the entry file for bundle generation - * entryFile: "index.android.js", - * - * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format - * bundleCommand: "ram-bundle", - * - * // whether to bundle JS and assets in debug mode - * bundleInDebug: false, - * - * // whether to bundle JS and assets in release mode - * bundleInRelease: true, - * - * // whether to bundle JS and assets in another build variant (if configured). - * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants - * // The configuration property can be in the following formats - * // 'bundleIn${productFlavor}${buildType}' - * // 'bundleIn${buildType}' - * // bundleInFreeDebug: true, - * // bundleInPaidRelease: true, - * // bundleInBeta: true, - * - * // whether to disable dev mode in custom build variants (by default only disabled in release) - * // for example: to disable dev mode in the staging build type (if configured) - * devDisabledInStaging: true, - * // The configuration property can be in the following formats - * // 'devDisabledIn${productFlavor}${buildType}' - * // 'devDisabledIn${buildType}' - * - * // the root of your project, i.e. where "package.json" lives - * root: "../../", - * - * // where to put the JS bundle asset in debug mode - * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", - * - * // where to put the JS bundle asset in release mode - * jsBundleDirRelease: "$buildDir/intermediates/assets/release", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in debug mode - * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in release mode - * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", - * - * // by default the gradle tasks are skipped if none of the JS files or assets change; this means - * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to - * // date; if you have any other folders that you want to ignore for performance reasons (gradle - * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ - * // for example, you might want to remove it from here. - * inputExcludes: ["android/**", "ios/**"], - * - * // override which node gets called and with what additional arguments - * nodeExecutableAndArgs: ["node"], - * - * // supply additional arguments to the packager - * extraPackagerArgs: [] - * ] - */ - -task buildReactNativeBundle(type:Exec) { - println("Building React Native bundle") - workingDir new File(rootProject.projectDir, '../') - commandLine './bundle-android.sh' -} -preBuild.dependsOn buildReactNativeBundle - -task printVersionName { - doLast { - println android.defaultConfig.versionName - } -} - -project.ext.react = [ - entryFile: "index.js", - enableHermes: false, // clean and rebuild if changing -] - -/** - * Set this to true to create two separate APKs instead of one: - * - An APK that only works on ARM devices - * - An APK that only works on x86 devices - * The advantage is the size of the APK is reduced by about 4MB. - * Upload all the APKs to the Play Store and people will download - * the correct one based on the CPU architecture of their device. - */ -def enableSeparateBuildPerCPUArchitecture = false - -/** - * Run Proguard to shrink the Java bytecode in release builds. - */ -def enableProguardInReleaseBuilds = false - -/** - * The preferred build flavor of JavaScriptCore. - * - * For example, to use the international variant, you can use: - * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` - * - * The international variant includes ICU i18n library and necessary data - * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that - * give correct results when using with locales other than en-US. Note that - * this variant is about 6MiB larger per architecture than default. - */ -def jscFlavor = 'org.webkit:android-jsc:+' - -/** - * Whether to enable the Hermes VM. - * - * This should be set on project.ext.react and mirrored here. If it is not set - * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode - * and the benefits of using Hermes will therefore be sharply reduced. - */ -def enableHermes = project.ext.react.get("enableHermes", false); +apply plugin: 'com.android.application' android { - compileSdkVersion rootProject.ext.compileSdkVersion + compileSdkVersion 29 + buildToolsVersion "29.0.1" flavorDimensions "default" compileOptions { @@ -141,18 +12,14 @@ android { defaultConfig { applicationId "io.lbry.browser" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1403 - versionName "0.14.3" - missingDimensionStrategy 'react-native-camera', 'general' - multiDexEnabled true - } - dexOptions { - javaMaxHeapSize "2048M" - preDexLibraries false - jumboMode true + minSdkVersion 21 + targetSdkVersion 29 + versionCode 1503 + versionName "0.15.3" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + productFlavors { __32bit { versionCode android.defaultConfig.versionCode * 10 + 1 @@ -167,66 +34,70 @@ android { } } } - signingConfigs { - debug { - storeFile file('debug.keystore') - storePassword 'android' - keyAlias 'androiddebugkey' - keyPassword 'android' + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } - buildTypes { - debug { - signingConfig signingConfigs.debug - } - release { - } +} + +task printVersionName { + doLast { + println android.defaultConfig.versionName } } dependencies { - implementation project(':@react-native-community_async-storage') - implementation project(':react-native-camera') - implementation project(':react-native-exception-handler') - implementation project(':react-native-fast-image') - implementation project(':react-native-fs') - implementation project(':react-native-gesture-handler') - implementation project(':react-native-reanimated') - implementation project(':react-native-snackbar') - implementation project(':react-native-video') - implementation project(':react-native-webview') - implementation project(':rn-fetch-blob') + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.media:media:1.0.0' - implementation 'androidx.appcompat:appcompat:1.0.0' - implementation 'com.facebook.react:react-native:0.61.5' - implementation 'com.facebook.fresco:fresco:1.9.0' - implementation 'com.facebook.fresco:animated-gif:1.9.0' - implementation 'com.squareup.picasso:picasso:2.71828' - implementation 'com.google.firebase:firebase-analytics:17.2.1' - implementation 'com.google.android.gms:play-services-base:17.1.0' - implementation 'androidx.exifinterface:exifinterface:1.0.0' - implementation 'com.facebook.fresco:animated-base-support:1.3.0' - implementation 'com.facebook.fresco:animated-gif:1.10.0' - implementation 'com.google.firebase:firebase-messaging:20.1.0' + implementation 'com.google.android.material:material:1.1.0' + implementation "androidx.cardview:cardview:1.0.0" + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.navigation:navigation-fragment:2.2.2' + implementation 'androidx.navigation:navigation-ui:2.2.2' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' + implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.camera:camera-camera2:1.0.0-beta03' + implementation 'androidx.camera:camera-lifecycle:1.0.0-beta03' + implementation 'androidx.camera:camera-view:1.0.0-alpha10' - __32bitImplementation files('libs/lbrysdk-0.73.1-release__arm.aar') - __64bitImplementation files('libs/lbrysdk-0.73.1-release__arm64.aar') + implementation 'com.github.bumptech.glide:glide:4.11.0' + implementation 'com.squareup.okhttp3:okhttp:4.4.1' + implementation 'com.google.firebase:firebase-analytics:17.4.0' + implementation 'com.google.android.gms:play-services-base:17.2.1' + implementation 'com.google.firebase:firebase-messaging:20.1.6' - if (enableHermes) { - def hermesPath = "../../node_modules/hermes-engine/android/"; - debugImplementation files(hermesPath + "hermes-debug.aar") - releaseImplementation files(hermesPath + "hermes-release.aar") - } else { - implementation jscFlavor - } -} + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.google.android.exoplayer:exoplayer-core:2.11.4' + implementation 'com.google.android.exoplayer:exoplayer-dash:2.11.4' + implementation 'com.google.android.exoplayer:exoplayer-ui:2.11.4' + implementation 'com.google.android.exoplayer:extension-cast:2.11.4' + implementation 'com.google.android.exoplayer:extension-mediasession:2.11.4' -// Run this once to be able to run the application with BUCK -// puts all compile dependencies into folder libs for BUCK to use -task copyDownloadableDepsToLibs(type: Copy) { - from configurations.compile - into 'libs' + implementation 'com.google.android:flexbox:2.0.1' + + implementation 'com.hbb20:ccp:2.3.8' + + implementation 'com.github.chrisbanes:PhotoView:2.3.0' + implementation 'com.atlassian.commonmark:commonmark:0.14.0' + + implementation 'com.arthenica:mobile-ffmpeg-full-gpl:4.3.1.LTS' + + compileOnly 'org.projectlombok:lombok:1.18.10' + annotationProcessor 'org.projectlombok:lombok:1.18.10' + annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' + + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + __32bitImplementation files('libs/lbrysdk-0.74.0-release__arm.aar') + __64bitImplementation files('libs/lbrysdk-0.74.0-release__arm64.aar') } apply plugin: 'com.google.gms.google-services' diff --git a/app/build_defs.bzl b/app/build_defs.bzl deleted file mode 100644 index fff270f8..00000000 --- a/app/build_defs.bzl +++ /dev/null @@ -1,19 +0,0 @@ -"""Helper definitions to glob .aar and .jar targets""" - -def create_aar_targets(aarfiles): - for aarfile in aarfiles: - name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] - lib_deps.append(":" + name) - android_prebuilt_aar( - name = name, - aar = aarfile, - ) - -def create_jar_targets(jarfiles): - for jarfile in jarfiles: - name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] - lib_deps.append(":" + name) - prebuilt_jar( - name = name, - binary_jar = jarfile, - ) diff --git a/app/debug.keystore b/app/debug.keystore deleted file mode 100644 index 364e105e..00000000 Binary files a/app/debug.keystore and /dev/null differ diff --git a/app/libs/lbrysdk-0.73.1-release__arm.aar b/app/libs/lbrysdk-0.74.0-release__arm.aar similarity index 70% rename from app/libs/lbrysdk-0.73.1-release__arm.aar rename to app/libs/lbrysdk-0.74.0-release__arm.aar index 6058d44a..f917e371 100644 Binary files a/app/libs/lbrysdk-0.73.1-release__arm.aar and b/app/libs/lbrysdk-0.74.0-release__arm.aar differ diff --git a/app/libs/lbrysdk-0.73.1-release__arm64.aar b/app/libs/lbrysdk-0.74.0-release__arm64.aar similarity index 71% rename from app/libs/lbrysdk-0.73.1-release__arm64.aar rename to app/libs/lbrysdk-0.74.0-release__arm64.aar index 525e693d..fe87414a 100644 Binary files a/app/libs/lbrysdk-0.73.1-release__arm64.aar and b/app/libs/lbrysdk-0.74.0-release__arm64.aar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 11b02572..f1b42451 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,10 +1,21 @@ # Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html -# Add any project specific keep options here: +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/io/lbry/browser/ExampleInstrumentedTest.java b/app/src/androidTest/java/io/lbry/browser/ExampleInstrumentedTest.java new file mode 100644 index 00000000..50f333a6 --- /dev/null +++ b/app/src/androidTest/java/io/lbry/browser/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package io.lbry.browser; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("io.lbry.browser", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a08ad7b2..28eca8a7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,30 +1,30 @@ + - + - - + + @@ -37,16 +37,18 @@ - - - + android:supportsPictureInPicture="true" + android:theme="@style/AppTheme.NoActionBar" + android:launchMode="singleTask" + android:windowSoftInputMode="adjustResize"> + + + + @@ -54,14 +56,19 @@ - - - - - - + + - - + \ No newline at end of file diff --git a/app/src/main/assets/font_awesome_5_free_solid.otf b/app/src/main/assets/font_awesome_5_free_solid.otf new file mode 100644 index 00000000..9d8a0e62 Binary files /dev/null and b/app/src/main/assets/font_awesome_5_free_solid.otf differ diff --git a/app/src/main/assets/fonts/Feather.ttf b/app/src/main/assets/fonts/Feather.ttf deleted file mode 100644 index 852c7135..00000000 Binary files a/app/src/main/assets/fonts/Feather.ttf and /dev/null differ diff --git a/app/src/main/assets/fonts/FontAwesome.ttf b/app/src/main/assets/fonts/FontAwesome.ttf deleted file mode 100644 index 35acda2f..00000000 Binary files a/app/src/main/assets/fonts/FontAwesome.ttf and /dev/null differ diff --git a/app/src/main/assets/fonts/FontAwesome5_Brands.ttf b/app/src/main/assets/fonts/FontAwesome5_Brands.ttf deleted file mode 100644 index 5f72e912..00000000 Binary files a/app/src/main/assets/fonts/FontAwesome5_Brands.ttf and /dev/null differ diff --git a/app/src/main/assets/fonts/FontAwesome5_Regular.ttf b/app/src/main/assets/fonts/FontAwesome5_Regular.ttf deleted file mode 100644 index a309313d..00000000 Binary files a/app/src/main/assets/fonts/FontAwesome5_Regular.ttf and /dev/null differ diff --git a/app/src/main/assets/fonts/FontAwesome5_Solid.ttf b/app/src/main/assets/fonts/FontAwesome5_Solid.ttf deleted file mode 100644 index 7ece3282..00000000 Binary files a/app/src/main/assets/fonts/FontAwesome5_Solid.ttf and /dev/null differ diff --git a/app/src/main/assets/fonts/Inter-Medium.otf b/app/src/main/assets/fonts/Inter-Medium.otf deleted file mode 100644 index 1bcb0a93..00000000 Binary files a/app/src/main/assets/fonts/Inter-Medium.otf and /dev/null differ diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 00000000..a82c512a Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/io/lbry/browser/DownloadManager.java b/app/src/main/java/io/lbry/browser/DownloadManager.java deleted file mode 100644 index 1d8e82ab..00000000 --- a/app/src/main/java/io/lbry/browser/DownloadManager.java +++ /dev/null @@ -1,415 +0,0 @@ -package io.lbry.browser; - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.ContextCompat; - -import io.lbry.browser.receivers.NotificationDeletedReceiver; -import io.lbry.lbrysdk.LbrynetService; - -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.List; -import java.util.Random; - -public class DownloadManager { - private Context context; - - private List activeDownloads = new ArrayList(); - - private List completedDownloads = new ArrayList(); - - private Map downloadIdOutpointsMap = new HashMap(); - - // maintain a map of uris to writtenBytes, so that we check if it's changed and don't flood RN with update events every 500ms - private Map writtenDownloadBytes = new HashMap(); - - private HashMap builders = new HashMap(); - - private HashMap downloadIdNotificationIdMap = new HashMap(); - - private HashMap stoppedDownloadsMap = new HashMap(); - - private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#"); - - private static final int MAX_FILENAME_LENGTH = 20; - - private static final int MAX_PROGRESS = 100; - - private static final String GROUP_DOWNLOADS = "io.lbry.browser.GROUP_DOWNLOADS"; - - private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.DOWNLOADS_NOTIFICATION_CHANNEL"; - - private static boolean channelCreated = false; - - private static NotificationCompat.Builder groupBuilder = null; - - public static final String NOTIFICATION_ID_KEY = "io.lbry.browser.notificationId"; - - public static final String ACTION_DOWNLOAD_EVENT = "io.lbry.browser.ACTION_DOWNLOAD_EVENT"; - - public static final String ACTION_START = "start"; - - public static final String ACTION_COMPLETE = "complete"; - - public static final String ACTION_UPDATE = "update"; - - public static final int DOWNLOAD_NOTIFICATION_GROUP_ID = 20; - - public static boolean groupCreated = false; - - public DownloadManager(Context context) { - this.context = context; - } - - private int generateNotificationId() { - int id = 0; - Random random = new Random(); - do { - id = random.nextInt(); - } while (id < 1000); - - return id; - } - - private void createNotificationChannel() { - // Only applies to Android 8.0 Oreo (API Level 26) or higher - if (!channelCreated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationManager notificationManager = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - NotificationChannel channel = new NotificationChannel( - NOTIFICATION_CHANNEL_ID, "LBRY Downloads", NotificationManager.IMPORTANCE_LOW); - channel.setDescription("LBRY file downloads"); - channel.setSound(null, null); - notificationManager.createNotificationChannel(channel); - } - } - - private void createNotificationGroup() { - if (!groupCreated) { - Intent intent = new Intent(context, NotificationDeletedReceiver.class); - intent.putExtra(NOTIFICATION_ID_KEY, DOWNLOAD_NOTIFICATION_GROUP_ID); - - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, DOWNLOAD_NOTIFICATION_GROUP_ID, intent, 0); - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - groupBuilder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID); - groupBuilder.setContentTitle("Active LBRY downloads") - // contentText will be displayed if there are no notifications in the group - .setContentText("There are no active LBRY downloads.") - .setSmallIcon(android.R.drawable.stat_sys_download) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setGroup(GROUP_DOWNLOADS) - .setGroupSummary(true) - .setDeleteIntent(pendingIntent); - notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build()); - - groupCreated = true; - } - } - - public static PendingIntent getLaunchPendingIntent(String uri, Context context) { - Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); - launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - PendingIntent intent = PendingIntent.getActivity(context, 0, launchIntent, 0); - return intent; - } - - public void updateWrittenBytesForDownload(String id, double writtenBytes) { - if (!writtenDownloadBytes.containsKey(id)) { - writtenDownloadBytes.put(id, writtenBytes); - } - } - - public double getWrittenBytesForDownload(String id) { - if (writtenDownloadBytes.containsKey(id)) { - return writtenDownloadBytes.get(id); - } - - return -1; - } - - public void clearWrittenBytesForDownload(String id) { - if (writtenDownloadBytes.containsKey(id)) { - writtenDownloadBytes.remove(id); - } - } - - private Intent getDeleteDownloadIntent(String uri) { - Intent intent = new Intent(); - intent.setAction(LbrynetService.ACTION_DELETE_DOWNLOAD); - intent.putExtra("uri", uri); - intent.putExtra("nativeDelete", true); - return intent; - } - - public void startDownload(String id, String filename, String outpoint) { - if (filename == null || filename.trim().length() == 0) { - return; - } - - synchronized (this) { - if (!isDownloadActive(id)) { - activeDownloads.add(id); - downloadIdOutpointsMap.put(id, outpoint); - } - - createNotificationChannel(); - createNotificationGroup(); - - PendingIntent stopDownloadIntent = PendingIntent.getBroadcast(context, 0, getDeleteDownloadIntent(id), PendingIntent.FLAG_CANCEL_CURRENT); - - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID); - // The file URI is used as the unique ID - builder.setColor(ContextCompat.getColor(context, R.color.lbryGreen)) - .setContentIntent(getLaunchPendingIntent(id, context)) - .setContentTitle(String.format("Downloading %s", truncateFilename(filename))) - .setGroup(GROUP_DOWNLOADS) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setProgress(MAX_PROGRESS, 0, false) - .setSmallIcon(android.R.drawable.stat_sys_download) - .setOngoing(true) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopDownloadIntent); - - int notificationId = getNotificationId(id); - downloadIdNotificationIdMap.put(id, notificationId); - builders.put(notificationId, builder); - notificationManager.notify(notificationId, builder.build()); - - if (groupCreated && groupBuilder != null) { - groupBuilder.setSmallIcon(android.R.drawable.stat_sys_download); - notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build()); - } - } - } - - public void updateDownload(String id, String filename, double writtenBytes, double totalBytes) { - if (filename == null || filename.trim().length() == 0) { - return; - } - - synchronized (this) { - createNotificationChannel(); - createNotificationGroup(); - - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - NotificationCompat.Builder builder = null; - int notificationId = getNotificationId(id); - if (builders.containsKey(notificationId)) { - builder = builders.get(notificationId); - } else { - PendingIntent stopDownloadIntent = PendingIntent.getBroadcast(context, 0, getDeleteDownloadIntent(id), PendingIntent.FLAG_CANCEL_CURRENT); - builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID); - builder.setColor(ContextCompat.getColor(context, R.color.lbryGreen)) - .setContentTitle(String.format("Downloading %s", truncateFilename(filename))) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setOngoing(true) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopDownloadIntent); - builders.put(notificationId, builder); - } - - double progress = (writtenBytes / totalBytes) * 100; - builder.setContentIntent(getLaunchPendingIntent(id, context)) - .setContentText(String.format("%.0f%% (%s / %s)", progress, formatBytes(writtenBytes), formatBytes(totalBytes))) - .setGroup(GROUP_DOWNLOADS) - .setProgress(MAX_PROGRESS, new Double(progress).intValue(), false) - .setSmallIcon(android.R.drawable.stat_sys_download); - notificationManager.notify(notificationId, builder.build()); - - if (progress >= MAX_PROGRESS) { - builder.setContentTitle(String.format("Downloaded %s", truncateFilename(filename, 30))) - .setContentText(String.format("%s", formatBytes(totalBytes))) - .setGroup(GROUP_DOWNLOADS) - .setProgress(0, 0, false) - .setSmallIcon(android.R.drawable.stat_sys_download_done) - .setOngoing(false); - builder.mActions.clear(); - notificationManager.notify(notificationId, builder.build()); - - if (downloadIdNotificationIdMap.containsKey(id)) { - downloadIdNotificationIdMap.remove(id); - } - if (builders.containsKey(notificationId)) { - builders.remove(notificationId); - } - - // If there are no more downloads and the group exists, set the icon to stop animating - if (groupCreated && groupBuilder != null && downloadIdNotificationIdMap.size() == 0) { - groupBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done); - notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build()); - } - - completeDownload(id, filename, totalBytes); - } - } - } - - public void completeDownload(String id, String filename, double totalBytes) { - synchronized (this) { - if (isDownloadActive(id)) { - activeDownloads.remove(id); - } - if (!isDownloadCompleted(id)) { - completedDownloads.add(id); - } - - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - NotificationCompat.Builder builder = null; - int notificationId = getNotificationId(id); - if (builders.containsKey(notificationId)) { - builder = builders.get(notificationId); - } else { - builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID); - builder.setPriority(NotificationCompat.PRIORITY_LOW); - builders.put(notificationId, builder); - } - - builder.setContentTitle(String.format("Downloaded %s", truncateFilename(filename, 30))) - .setContentText(String.format("%s", formatBytes(totalBytes))) - .setGroup(GROUP_DOWNLOADS) - .setProgress(0, 0, false) - .setSmallIcon(android.R.drawable.stat_sys_download_done) - .setOngoing(false); - builder.mActions.clear(); - notificationManager.notify(notificationId, builder.build()); - - // If there are no more downloads and the group exists, set the icon to stop animating - checkGroupDownloadIcon(notificationManager); - } - } - - public void abortDownload(String id) { - synchronized (this) { - if (downloadIdNotificationIdMap.containsKey(id)) { - removeDownloadNotification(id); - } - activeDownloads.remove(id); - } - } - - public boolean isDownloadActive(String id) { - return (activeDownloads.contains(id)); - } - - public boolean isDownloadCompleted(String id) { - return (completedDownloads.contains(id)); - } - - public boolean hasActiveDownloads() { - return activeDownloads.size() > 0; - } - - public List getActiveDownloads() { - return activeDownloads; - } - - public List getCompletedDownloads() { - return completedDownloads; - } - - public String getOutpointForDownload(String uri) { - if (downloadIdOutpointsMap.containsKey(uri)) { - return downloadIdOutpointsMap.get(uri); - } - - return null; - } - - public void deleteDownloadUri(String uri) { - synchronized (this) { - activeDownloads.remove(uri); - completedDownloads.remove(uri); - - if (downloadIdOutpointsMap.containsKey(uri)) { - downloadIdOutpointsMap.remove(uri); - } - if (downloadIdNotificationIdMap.containsKey(uri)) { - removeDownloadNotification(uri); - } - } - } - - private void removeDownloadNotification(String id) { - int notificationId = downloadIdNotificationIdMap.get(id); - if (downloadIdNotificationIdMap.containsKey(id)) { - downloadIdNotificationIdMap.remove(id); - } - if (builders.containsKey(notificationId)) { - builders.remove(notificationId); - } - - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - NotificationCompat.Builder builder = builders.get(notificationId); - notificationManager.cancel(notificationId); - - checkGroupDownloadIcon(notificationManager); - if (builders.values().size() == 0) { - notificationManager.cancel(DOWNLOAD_NOTIFICATION_GROUP_ID); - groupCreated = false; - } - } - - private int getNotificationId(String id) { - if (downloadIdNotificationIdMap.containsKey(id)) { - return downloadIdNotificationIdMap.get(id); - } - - int notificationId = generateNotificationId(); - if (MainActivity.downloadNotificationIds != null && - !MainActivity.downloadNotificationIds.contains(notificationId)) { - MainActivity.downloadNotificationIds.add(notificationId); - } - downloadIdNotificationIdMap.put(id, notificationId); - return notificationId; - } - - private void checkGroupDownloadIcon(NotificationManagerCompat notificationManager) { - if (groupCreated && groupBuilder != null && downloadIdNotificationIdMap.size() == 0) { - groupBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done); - notificationManager.notify(DOWNLOAD_NOTIFICATION_GROUP_ID, groupBuilder.build()); - } - } - - private static String formatBytes(double bytes) - { - if (bytes < 1048576) { // < 1MB - return String.format("%s KB", DECIMAL_FORMAT.format(bytes / 1024.0)); - } - - if (bytes < 1073741824) { // < 1GB - return String.format("%s MB", DECIMAL_FORMAT.format(bytes / (1024.0 * 1024.0))); - } - - return String.format("%s GB", DECIMAL_FORMAT.format(bytes / (1024.0 * 1024.0 * 1024.0))); - } - - private static String truncateFilename(String filename, int alternateMaxLength) { - int maxLength = alternateMaxLength > 0 ? alternateMaxLength : MAX_FILENAME_LENGTH; - if (filename.length() < maxLength) { - return filename; - } - - // Get the extension - int dotIndex = filename.lastIndexOf("."); - if (dotIndex > -1) { - String extension = filename.substring(dotIndex); - return String.format("%s...%s", filename.substring(0, maxLength - extension.length() - 4), extension); - } - - return String.format("%s...", filename.substring(0, maxLength - 3)); - } - - private static String truncateFilename(String filename) { - return truncateFilename(filename, 0); - } -} diff --git a/app/src/main/java/io/lbry/browser/FirstRunActivity.java b/app/src/main/java/io/lbry/browser/FirstRunActivity.java new file mode 100644 index 00000000..8197fd70 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/FirstRunActivity.java @@ -0,0 +1,138 @@ +package io.lbry.browser; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.text.HtmlCompat; +import androidx.preference.PreferenceManager; + +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.Lbryio; + +public class FirstRunActivity extends AppCompatActivity { + + private BroadcastReceiver sdkReadyReceiver; + private BroadcastReceiver authReceiver; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_first_run); + + TextView welcomeTos = findViewById(R.id.welcome_text_view_tos); + welcomeTos.setMovementMethod(LinkMovementMethod.getInstance()); + welcomeTos.setText(HtmlCompat.fromHtml(getString(R.string.welcome_tos), HtmlCompat.FROM_HTML_MODE_LEGACY)); + + findViewById(R.id.welcome_link_use_lbry).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + finishFirstRun(); + } + }); + + registerAuthReceiver(); + if (!Lbry.SDK_READY) { + findViewById(R.id.welcome_wait_container).setVisibility(View.VISIBLE); + IntentFilter filter = new IntentFilter(); + filter.addAction(MainActivity.ACTION_SDK_READY); + sdkReadyReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // authenticate after we receive the sdk ready event + authenticate(); + } + }; + registerReceiver(sdkReadyReceiver, filter); + } else { + authenticate(); + } + } + + public void onResume() { + super.onResume(); + LbryAnalytics.setCurrentScreen(this, "First Run", "FirstRun"); + } + + private void registerAuthReceiver() { + IntentFilter filter = new IntentFilter(); + filter.addAction(MainActivity.ACTION_USER_AUTHENTICATION_SUCCESS); + filter.addAction(MainActivity.ACTION_USER_AUTHENTICATION_FAILED); + authReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (MainActivity.ACTION_USER_AUTHENTICATION_SUCCESS.equals(intent.getAction())) { + handleAuthenticationSuccess(); + } else { + handleAuthenticationFailed(); + } + } + }; + registerReceiver(authReceiver, filter); + } + + private void handleAuthenticationSuccess() { + // first_auth completed event + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + boolean firstAuthCompleted = sp.getBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_FIRST_AUTH_COMPLETED, false); + if (!firstAuthCompleted) { + LbryAnalytics.logEvent(LbryAnalytics.EVENT_FIRST_USER_AUTH); + sp.edit().putBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_FIRST_AUTH_COMPLETED, true).apply(); + } + + findViewById(R.id.welcome_wait_container).setVisibility(View.GONE); + findViewById(R.id.welcome_display).setVisibility(View.VISIBLE); + findViewById(R.id.welcome_link_use_lbry).setVisibility(View.VISIBLE); + } + + private void handleAuthenticationFailed() { + Toast.makeText(this, "Authentication failed.", Toast.LENGTH_LONG).show(); + } + + private void authenticate() { + new AuthenticateTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void finishFirstRun() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + sp.edit().putBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_FIRST_RUN_COMPLETED, true).apply(); + + // first_run_completed event + LbryAnalytics.logEvent(LbryAnalytics.EVENT_FIRST_RUN_COMPLETED); + finish(); + } + + @Override + public void onBackPressed() { + return; + } + + @Override + protected void onDestroy() { + Helper.unregisterReceiver(authReceiver, this); + Helper.unregisterReceiver(sdkReadyReceiver, this); + super.onDestroy(); + } + + private static class AuthenticateTask extends AsyncTask { + private Context context; + public AuthenticateTask(Context context) { + this.context = context; + } + protected Void doInBackground(Void... params) { + Lbryio.authenticate(context); + return null; + } + } +} diff --git a/app/src/main/java/io/lbry/browser/LbrynetMessagingService.java b/app/src/main/java/io/lbry/browser/LbrynetMessagingService.java index f5c4c4a8..62536467 100644 --- a/app/src/main/java/io/lbry/browser/LbrynetMessagingService.java +++ b/app/src/main/java/io/lbry/browser/LbrynetMessagingService.java @@ -12,14 +12,16 @@ import android.os.Build; import android.os.Bundle; import androidx.core.app.NotificationCompat; import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; + import android.util.Log; import com.google.firebase.analytics.FirebaseAnalytics; import com.google.firebase.messaging.FirebaseMessagingService; import com.google.firebase.messaging.RemoteMessage; +import io.lbry.browser.utils.LbryAnalytics; import io.lbry.lbrysdk.LbrynetService; -import io.lbry.browser.reactmodules.UtilityModule; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @@ -30,22 +32,15 @@ import java.util.Map; public class LbrynetMessagingService extends FirebaseMessagingService { private static final String TAG = "LbrynetMessagingService"; - private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.LBRY_ENGAGEMENT_CHANNEL"; - private static final String TYPE_SUBSCRIPTION = "subscription"; - private static final String TYPE_REWARD = "reward"; - private static final String TYPE_INTERESTS = "interests"; - private static final String TYPE_CREATOR = "creator"; - private FirebaseAnalytics firebaseAnalytics; @Override public void onMessageReceived(RemoteMessage remoteMessage) { - Log.d(TAG, "From: " + remoteMessage.getFrom()); if (firebaseAnalytics == null) { firebaseAnalytics = FirebaseAnalytics.getInstance(this); } @@ -67,7 +62,7 @@ public class LbrynetMessagingService extends FirebaseMessagingService { if (firebaseAnalytics != null) { Bundle bundle = new Bundle(); bundle.putString("name", name); - firebaseAnalytics.logEvent("lbry_notification_receive", bundle); + firebaseAnalytics.logEvent(LbryAnalytics.EVENT_LBRY_NOTIFICATION_RECEIVE, bundle); } sendNotification(title, body, type, url, name, contentTitle, channelUrl, publishTime); @@ -97,7 +92,7 @@ public class LbrynetMessagingService extends FirebaseMessagingService { // TODO: Implement this method to send token to your app server. } - /** + /** * Create and show a simple notification containing the received FCM message. * * @param messageBody FCM message body received. @@ -113,29 +108,6 @@ public class LbrynetMessagingService extends FirebaseMessagingService { // default to home page url = "lbry://?discover"; } - } else { - if (!MainActivity.isServiceRunning(this, LbrynetService.class) && - contentTitle != null && - channelUrl != null && - !url.startsWith("lbry://?") /* not a special url */ - ) { - // only enter lite mode when contentTitle and channelUrl are set (and the service isn't running yet) - // cold start - url = url + ((url.indexOf("?") > -1) ? "&liteMode=1" : "?liteMode=1"); - try { - if (contentTitle != null) { - url = url + "&contentTitle=" + URLEncoder.encode(contentTitle, "UTF-8"); - } - if (channelUrl != null) { - url = url + "&channelUrl=" + URLEncoder.encode(channelUrl, "UTF-8"); - } - if (publishTime != null) { - url = url + "&publishTime=" + URLEncoder.encode(publishTime, "UTF-8"); - } - } catch (UnsupportedEncodingException ex) { - // shouldn't happen - } - } } Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); @@ -160,27 +132,27 @@ public class LbrynetMessagingService extends FirebaseMessagingService { // Since android Oreo notification channel is needed. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( - NOTIFICATION_CHANNEL_ID, "LBRY Engagement", NotificationManager.IMPORTANCE_DEFAULT); + NOTIFICATION_CHANNEL_ID, "LBRY Engagement", NotificationManager.IMPORTANCE_DEFAULT); notificationManager.createNotificationChannel(channel); } - notificationManager.notify(9898, notificationBuilder.build()); + notificationManager.notify(3, notificationBuilder.build()); } public List getEnabledTypes() { - SharedPreferences sp = getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); List enabledTypes = new ArrayList(); - if (sp.getBoolean(UtilityModule.RECEIVE_SUBSCRIPTION_NOTIFICATIONS, true)) { + if (sp.getBoolean(MainActivity.PREFERENCE_KEY_NOTIFICATION_SUBSCRIPTIONS, true)) { enabledTypes.add(TYPE_SUBSCRIPTION); } - if (sp.getBoolean(UtilityModule.RECEIVE_REWARD_NOTIFICATIONS, true)) { + if (sp.getBoolean(MainActivity.PREFERENCE_KEY_NOTIFICATION_REWARDS, true)) { enabledTypes.add(TYPE_REWARD); } - if (sp.getBoolean(UtilityModule.RECEIVE_INTERESTS_NOTIFICATIONS, true)) { + if (sp.getBoolean(MainActivity.PREFERENCE_KEY_NOTIFICATION_CONTENT_INTERESTS, true)) { enabledTypes.add(TYPE_INTERESTS); } - if (sp.getBoolean(UtilityModule.RECEIVE_CREATOR_NOTIFICATIONS, true)) { + if (sp.getBoolean(MainActivity.PREFERENCE_KEY_NOTIFICATION_CREATOR, true)) { enabledTypes.add(TYPE_CREATOR); } diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index f5b0d6f0..ed711318 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -1,193 +1,2395 @@ package io.lbry.browser; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; +import android.app.Activity; +import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.app.Activity; -import android.app.ActivityManager; +import android.app.PictureInPictureParams; import android.content.BroadcastReceiver; -import android.content.ContentUris; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; import android.content.SharedPreferences; -import android.database.Cursor; -import android.Manifest; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; import android.os.Handler; -import android.provider.DocumentsContract; import android.provider.MediaStore; -import android.provider.Settings; +import android.support.v4.media.session.MediaSessionCompat; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Base64; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.Menu; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.ext.cast.CastPlayer; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; +import com.google.android.exoplayer2.ui.PlayerView; +import com.google.android.exoplayer2.upstream.cache.Cache; +import com.google.android.gms.cast.framework.CastContext; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.android.material.snackbar.Snackbar; +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.InstanceIdResult; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AppCompatDelegate; import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; -import androidx.fragment.app.FragmentActivity; -import android.telephony.SmsMessage; -import android.widget.Toast; +import androidx.core.content.FileProvider; +import androidx.core.content.res.ResourcesCompat; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.core.view.GravityCompat; +import androidx.core.view.OnApplyWindowInsetsListener; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; +import androidx.media.session.MediaButtonReceiver; +import androidx.preference.PreferenceManager; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; -import com.azendoo.reactnativesnackbar.SnackbarPackage; -import com.brentvatne.react.ReactVideoPackage; -import com.dylanvann.fastimage.FastImageViewPackage; -import com.facebook.react.common.LifecycleState; -import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; -import com.facebook.react.ReactRootView; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.facebook.react.modules.core.PermissionAwareActivity; -import com.facebook.react.modules.core.PermissionListener; -import com.facebook.react.shell.MainReactPackage; -import com.facebook.soloader.SoLoader; -import com.google.firebase.analytics.FirebaseAnalytics; -import com.reactnativecommunity.asyncstorage.AsyncStoragePackage; -import com.reactnativecommunity.webview.RNCWebViewPackage; -import com.rnfs.RNFSPackage; -import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; -import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; -import com.swmansion.reanimated.ReanimatedPackage; -import com.RNFetchBlob.RNFetchBlobPackage; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; -import io.lbry.browser.reactmodules.UtilityModule; -import io.lbry.browser.reactpackages.LbryReactPackage; -import io.lbry.browser.reactmodules.BackgroundMediaModule; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.net.ConnectException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import io.lbry.browser.adapter.NavigationMenuAdapter; +import io.lbry.browser.adapter.UrlSuggestionListAdapter; +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.dialog.ContentScopeDialogFragment; +import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.listener.CameraPermissionListener; +import io.lbry.browser.listener.DownloadActionListener; +import io.lbry.browser.listener.FetchChannelsListener; +import io.lbry.browser.listener.FetchClaimsListener; +import io.lbry.browser.listener.FilePickerListener; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.listener.StoragePermissionListener; +import io.lbry.browser.listener.WalletBalanceListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.ClaimCacheKey; +import io.lbry.browser.model.NavMenuItem; +import io.lbry.browser.model.Tag; +import io.lbry.browser.model.UrlSuggestion; +import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.model.WalletSync; +import io.lbry.browser.model.lbryinc.Reward; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimListTask; +import io.lbry.browser.tasks.lbryinc.ClaimRewardTask; +import io.lbry.browser.tasks.lbryinc.FetchRewardsTask; +import io.lbry.browser.tasks.LighthouseAutoCompleteTask; +import io.lbry.browser.tasks.MergeSubscriptionsTask; +import io.lbry.browser.tasks.claim.ResolveTask; +import io.lbry.browser.tasks.localdata.FetchRecentUrlHistoryTask; +import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler; +import io.lbry.browser.tasks.wallet.LoadSharedUserStateTask; +import io.lbry.browser.tasks.wallet.SaveSharedUserStateTask; +import io.lbry.browser.tasks.wallet.SyncApplyTask; +import io.lbry.browser.tasks.wallet.SyncGetTask; +import io.lbry.browser.tasks.wallet.SyncSetTask; +import io.lbry.browser.tasks.wallet.WalletBalanceTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.ui.channel.ChannelFormFragment; +import io.lbry.browser.ui.channel.ChannelFragment; +import io.lbry.browser.ui.channel.ChannelManagerFragment; +import io.lbry.browser.ui.findcontent.EditorsChoiceFragment; +import io.lbry.browser.ui.findcontent.FileViewFragment; +import io.lbry.browser.ui.findcontent.FollowingFragment; +import io.lbry.browser.ui.library.LibraryFragment; +import io.lbry.browser.ui.other.AboutFragment; +import io.lbry.browser.ui.publish.PublishFormFragment; +import io.lbry.browser.ui.publish.PublishFragment; +import io.lbry.browser.ui.publish.PublishesFragment; +import io.lbry.browser.ui.findcontent.SearchFragment; +import io.lbry.browser.ui.other.SettingsFragment; +import io.lbry.browser.ui.findcontent.AllContentFragment; +import io.lbry.browser.ui.wallet.InvitesFragment; +import io.lbry.browser.ui.wallet.RewardsFragment; +import io.lbry.browser.ui.wallet.WalletFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; +import io.lbry.lbrysdk.DownloadManager; import io.lbry.lbrysdk.LbrynetService; import io.lbry.lbrysdk.ServiceHelper; import io.lbry.lbrysdk.Utils; +import lombok.Getter; +import lombok.Setter; +import okhttp3.OkHttpClient; -import java.io.File; -import java.net.ConnectException; -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.List; +public class MainActivity extends AppCompatActivity implements SdkStatusListener { -import org.json.JSONObject; -import org.json.JSONArray; -import org.json.JSONException; -import org.reactnative.camera.RNCameraPackage; + private Map specialRouteFragmentClassMap; + private boolean inPictureInPictureMode; + public static SimpleExoPlayer appPlayer; + public static Cache playerCache; + public static boolean playerReassigned; + public static CastContext castContext; + public static CastPlayer castPlayer; + public static Claim nowPlayingClaim; + public static String nowPlayingClaimUrl; + public static boolean startingFilePickerActivity = false; + public static boolean startingShareActivity = false; + public static boolean startingPermissionRequest = false; + public static boolean startingSignInFlowActivity = false; + public static boolean startingCameraRequest = false; + private boolean enteringPIPMode = false; + private boolean fullSyncInProgress = false; + private int queuedSyncCount = 0; + private String cameraOutputFilename; -public class MainActivity extends FragmentActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity { + @Setter + private BackPressInterceptor backPressInterceptor; - private static Activity currentActivity = null; - private static final int OVERLAY_PERMISSION_REQ_CODE = 101; - private static final int STORAGE_PERMISSION_REQ_CODE = 201; - private static final int PHONE_STATE_PERMISSION_REQ_CODE = 202; - private static final int RECEIVE_SMS_PERMISSION_REQ_CODE = 203; - public static final int DOCUMENT_PICKER_RESULT_CODE = 301; - public static final String SHARED_PREFERENCES_NAME = "LBRY"; - public static final String SALT_KEY = "salt"; - public static final String DEVICE_ID_KEY = "deviceId"; - public static final String SOURCE_NOTIFICATION_ID_KEY = "sourceNotificationId"; - public static final String SETTING_KEEP_DAEMON_RUNNING = "keepDaemonRunning"; - public static List downloadNotificationIds = new ArrayList(); + @Getter + private String firebaseMessagingToken; - private BroadcastReceiver notificationsReceiver; - private BroadcastReceiver smsReceiver; + private Map openNavFragments; + private static final Map fragmentClassNavIdMap = new HashMap<>(); + static { + Logger.getLogger(OkHttpClient.class.getName()).setLevel(Level.FINE); + + fragmentClassNavIdMap.put(FollowingFragment.class, NavMenuItem.ID_ITEM_FOLLOWING); + fragmentClassNavIdMap.put(EditorsChoiceFragment.class, NavMenuItem.ID_ITEM_EDITORS_CHOICE); + fragmentClassNavIdMap.put(AllContentFragment.class, NavMenuItem.ID_ITEM_ALL_CONTENT); + + fragmentClassNavIdMap.put(PublishFragment.class, NavMenuItem.ID_ITEM_NEW_PUBLISH); + fragmentClassNavIdMap.put(ChannelManagerFragment.class, NavMenuItem.ID_ITEM_CHANNELS); + fragmentClassNavIdMap.put(LibraryFragment.class, NavMenuItem.ID_ITEM_LIBRARY); + fragmentClassNavIdMap.put(PublishesFragment.class, NavMenuItem.ID_ITEM_PUBLISHES); + + fragmentClassNavIdMap.put(WalletFragment.class, NavMenuItem.ID_ITEM_WALLET); + fragmentClassNavIdMap.put(RewardsFragment.class, NavMenuItem.ID_ITEM_REWARDS); + fragmentClassNavIdMap.put(InvitesFragment.class, NavMenuItem.ID_ITEM_INVITES); + + fragmentClassNavIdMap.put(SettingsFragment.class, NavMenuItem.ID_ITEM_SETTINGS); + fragmentClassNavIdMap.put(AboutFragment.class, NavMenuItem.ID_ITEM_ABOUT); + + // Internal (sub-)pages + fragmentClassNavIdMap.put(FileViewFragment.class, NavMenuItem.ID_ITEM_FOLLOWING); + fragmentClassNavIdMap.put(ChannelFragment.class, NavMenuItem.ID_ITEM_FOLLOWING); + fragmentClassNavIdMap.put(SearchFragment.class, NavMenuItem.ID_ITEM_FOLLOWING); + } + + public static final int REQUEST_STORAGE_PERMISSION = 1001; + public static final int REQUEST_CAMERA_PERMISSION = 1002; + public static final int REQUEST_SIMPLE_SIGN_IN = 2001; + public static final int REQUEST_WALLET_SYNC_SIGN_IN = 2002; + public static final int REQUEST_REWARDS_VERIFY_SIGN_IN = 2003; + + public static final int REQUEST_FILE_PICKER = 5001; + public static final int REQUEST_VIDEO_CAPTURE = 5002; + public static final int REQUEST_TAKE_PHOTO = 5003; + + // broadcast action names + public static final String ACTION_SDK_READY = "io.lbry.browser.Broadcast.SdkReady"; + public static final String ACTION_AUTH_TOKEN_GENERATED = "io.lbry.browser.Broadcast.AuthTokenGenerated"; + public static final String ACTION_USER_AUTHENTICATION_SUCCESS = "io.lbry.browser.Broadcast.UserAuthenticationSuccess"; + public static final String ACTION_USER_SIGN_IN_SUCCESS = "io.lbry.browser.Broadcast.UserSignInSuccess"; + public static final String ACTION_USER_AUTHENTICATION_FAILED = "io.lbry.browser.Broadcast.UserAuthenticationFailed"; + public static final String ACTION_NOW_PLAYING_CLAIM_UPDATED = "io.lbry.browser.Broadcast.NowPlayingClaimUpdated"; + public static final String ACTION_NOW_PLAYING_CLAIM_CLEARED = "io.lbry.browser.Broadcast.NowPlayingClaimCleared"; + public static final String ACTION_OPEN_ALL_CONTENT_TAG = "io.lbry.browser.Broadcast.OpenAllContentTag"; + public static final String ACTION_WALLET_BALANCE_UPDATED = "io.lbry.browser.Broadcast.WalletBalanceUpdated"; + public static final String ACTION_OPEN_CHANNEL_URL = "io.lbry.browser.Broadcast.OpenChannelUrl"; + public static final String ACTION_OPEN_WALLET_PAGE = "io.lbry.browser.Broadcast.OpenWalletPage"; + public static final String ACTION_OPEN_REWARDS_PAGE = "io.lbry.browser.Broadcast.OpenRewardsPage"; + public static final String ACTION_SAVE_SHARED_USER_STATE = "io.lbry.browser.Broadcast.SaveSharedUserState"; + + // preference keys + public static final String PREFERENCE_KEY_DARK_MODE = "io.lbry.browser.preference.userinterface.DarkMode"; + public static final String PREFERENCE_KEY_SHOW_MATURE_CONTENT = "io.lbry.browser.preference.userinterface.ShowMatureContent"; + public static final String PREFERENCE_KEY_SHOW_URL_SUGGESTIONS = "io.lbry.browser.preference.userinterface.UrlSuggestions"; + public static final String PREFERENCE_KEY_NOTIFICATION_SUBSCRIPTIONS = "io.lbry.browser.preference.notifications.Subscriptions"; + public static final String PREFERENCE_KEY_NOTIFICATION_REWARDS = "io.lbry.browser.preference.notifications.Rewards"; + public static final String PREFERENCE_KEY_NOTIFICATION_CONTENT_INTERESTS = "io.lbry.browser.preference.notifications.ContentInterests"; + public static final String PREFERENCE_KEY_NOTIFICATION_CREATOR = "io.lbry.browser.preference.notifications.Creator"; + public static final String PREFERENCE_KEY_KEEP_SDK_BACKGROUND = "io.lbry.browser.preference.other.KeepSdkInBackground"; + public static final String PREFERENCE_KEY_PARTICIPATE_DATA_NETWORK = "io.lbry.browser.preference.other.ParticipateInDataNetwork"; + + // Internal flags / setting preferences + public static final String PREFERENCE_KEY_INTERNAL_SKIP_WALLET_ACCOUNT = "io.lbry.browser.preference.internal.WalletSkipAccount"; + public static final String PREFERENCE_KEY_INTERNAL_WALLET_SYNC_ENABLED = "io.lbry.browser.preference.internal.WalletSyncEnabled"; + public static final String PREFERENCE_KEY_INTERNAL_WALLET_RECEIVE_ADDRESS = "io.lbry.browser.preference.internal.WalletReceiveAddress"; + public static final String PREFERENCE_KEY_INTERNAL_REWARDS_NOT_INTERESTED = "io.lbry.browser.preference.internal.RewardsNotInterested"; + public static final String PREFERENCE_KEY_INTERNAL_NEW_ANDROID_REWARD_CLAIMED = "io.lbry.browser.preference.internal.NewAndroidRewardClaimed"; + + public static final String PREFERENCE_KEY_INTERNAL_FIRST_RUN_COMPLETED = "io.lbry.browser.preference.internal.FirstRunCompleted"; + public static final String PREFERENCE_KEY_INTERNAL_FIRST_AUTH_COMPLETED = "io.lbry.browser.preference.internal.FirstAuthCompleted"; + + private final int CHECK_SDK_READY_INTERVAL = 1000; + + public static final String PREFERENCE_KEY_AUTH_TOKEN = "io.lbry.browser.Preference.AuthToken"; + + public static final String SECURE_VALUE_KEY_SAVED_PASSWORD = "io.lbry.browser.PX"; + + private static final String TAG = "io.lbry.browser.Main"; + + private NavigationMenuAdapter navMenuAdapter; + private UrlSuggestionListAdapter urlSuggestionListAdapter; + private List recentUrlHistory; + private boolean hasLoadedFirstBalance; + + // broadcast receivers private BroadcastReceiver serviceActionsReceiver; - private BroadcastReceiver downloadEventReceiver; - private FirebaseAnalytics firebaseAnalytics; - private ReactRootView mReactRootView; - private ReactInstanceManager mReactInstanceManager; + private BroadcastReceiver requestsReceiver; - /** - * Flag which indicates whether or not the service is running. Will be updated in the - * onResume method. - */ + private static boolean appStarted; private boolean serviceRunning; private CheckSdkReadyTask checkSdkReadyTask; + private MediaSessionCompat mediaSession; private boolean receivedStopService; - private PermissionListener permissionListener; - public static boolean lbrySdkReady; + private ActionBarDrawerToggle toggle; + private SyncSetTask syncSetTask = null; + private List pendingSyncSetQueue; + @Getter + private DatabaseHelper dbHelper; + private int selectedMenuItemId = -1; + private List cameraPermissionListeners; + private List downloadActionListeners; + private List filePickerListeners; + private List sdkStatusListeners; + private List storagePermissionListeners; + private List walletBalanceListeners; + private List fetchClaimsListeners; + private List fetchChannelsListeners; + @Getter + private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + private boolean walletBalanceUpdateScheduled; + private boolean shouldOpenUserSelectedMenuItem; + private boolean walletSyncScheduled; + private String pendingAllContentTag; + private String pendingChannelUrl; + private boolean pendingOpenWalletPage; + private boolean pendingOpenRewardsPage; + private boolean pendingFollowingReload; - protected String getMainComponentName() { - return "LBRYApp"; + // startup stages (to be able to determine how far a user made it if startup fails) + // and display a more useful message for troubleshooting + private static final int STARTUP_STAGE_INSTALL_ID_LOADED = 1; + private static final int STARTUP_STAGE_KNOWN_TAGS_LOADED = 2; + private static final int STARTUP_STAGE_EXCHANGE_RATE_LOADED = 3; + private static final int STARTUP_STAGE_USER_AUTHENTICATED = 4; + private static final int STARTUP_STAGE_NEW_INSTALL_DONE = 5; + private static final int STARTUP_STAGE_SUBSCRIPTIONS_LOADED = 6; + private static final int STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED = 7; + + public boolean isDarkMode() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + return sp.getBoolean(PREFERENCE_KEY_DARK_MODE, false); } - - public static LaunchTiming CurrentLaunchTiming; - + @Override protected void onCreate(Bundle savedInstanceState) { - CurrentLaunchTiming = new LaunchTiming(new Date()); + AppCompatDelegate.setDefaultNightMode(isDarkMode() ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); + + initKeyStore(); + loadAuthToken(); + + if (!isDarkMode()) { + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + initSpecialRouteMap(); + + LbryAnalytics.init(this); + FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(Task task) { + if (!task.isSuccessful()) { + return; + } + + // Get new Instance ID token + firebaseMessagingToken = task.getResult().getToken(); + } + }); + super.onCreate(savedInstanceState); - currentActivity = this; + dbHelper = new DatabaseHelper(this); + checkNotificationOpenIntent(getIntent()); + setContentView(R.layout.activity_main); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); - SoLoader.init(this, false); + // TODO: Check Google Play Services availability + // castContext = CastContext.getSharedInstance(this); - // Register the stop service receiver (so that we close the activity if the user requests the service to stop) + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.content_main), new OnApplyWindowInsetsListener() { + @Override + public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) { + ViewCompat.onApplyWindowInsets(findViewById(R.id.url_suggestions_container), + insets.replaceSystemWindowInsets(0, 0, 0, insets.getSystemWindowInsetBottom())); + return ViewCompat.onApplyWindowInsets(v, + insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, + 0, insets.getSystemWindowInsetBottom())); + } + }); + + // register receivers + registerRequestsReceiver(); registerServiceActionsReceiver(); - // Register SMS receiver for handling verification texts - registerSmsReceiver(); + View decorView = getWindow().getDecorView(); + decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() { + @Override + public void onSystemUiVisibilityChange(int visibility) { + if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { + // not fullscreen + } + } + }); - // Register the receiver to emit download events - registerDownloadEventReceiver(); + // setup uri bar + setupUriBar(); - // Start the sdk service if it is not started - // Check the dht setting - SharedPreferences sp = getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - LbrynetService.setDHTEnabled(sp.getBoolean(UtilityModule.DHT_ENABLED, false)); + /*View decorView = getWindow().getDecorView(); + decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() { + @Override + public void onSystemUiVisibilityChange(int visibility) { + if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { + findViewById(R.id.app_bar_main_container).setFitsSystemWindows(false); + findViewById(R.id.drawer_layout).setFitsSystemWindows(false); + } else { + findViewById(R.id.app_bar_main_container).setFitsSystemWindows(true); + findViewById(R.id.drawer_layout).setFitsSystemWindows(true); + } + } + });*/ + + // other + pendingSyncSetQueue = new ArrayList<>(); + openNavFragments = new HashMap<>(); + cameraPermissionListeners = new ArrayList<>(); + downloadActionListeners = new ArrayList<>(); + filePickerListeners = new ArrayList<>(); + sdkStatusListeners = new ArrayList<>(); + storagePermissionListeners = new ArrayList<>(); + walletBalanceListeners = new ArrayList<>(); + fetchClaimsListeners = new ArrayList<>(); + fetchChannelsListeners = new ArrayList<>(); + + sdkStatusListeners.add(this); + + FragmentManager fragmentManager = getSupportFragmentManager(); + fragmentManager.addOnBackStackChangedListener(backStackChangedListener); + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + toggle = new ActionBarDrawerToggle( + this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) { + public void onDrawerClosed(View view) { + super.onDrawerClosed(view); + if (shouldOpenUserSelectedMenuItem) { + openSelectedMenuItem(); + shouldOpenUserSelectedMenuItem = false; + } + } + + @Override + public void onDrawerSlide(View drawerView, float slideOffset) { + if (slideOffset != 0) { + clearWunderbarFocus(findViewById(R.id.wunderbar)); + } + super.onDrawerSlide(drawerView, slideOffset); + } + }; + drawer.addDrawerListener(toggle); + toggle.syncState(); + toggle.setToolbarNavigationClickListener((view) -> { + if (toggle != null && !toggle.isDrawerIndicatorEnabled()) { + FragmentManager manager = getSupportFragmentManager(); + if (manager != null) { + manager.popBackStack(); + setSelectedNavMenuItemForFragment(getCurrentFragment()); + } + } + }); + + findViewById(R.id.global_now_playing_close).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + stopExoplayer(); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + nowPlayingClaim = null; + findViewById(R.id.global_now_playing_card).setVisibility(View.GONE); + } + }); + + findViewById(R.id.global_now_playing_card).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (nowPlayingClaim != null && !Helper.isNullOrEmpty(nowPlayingClaimUrl)) { + openFileUrl(nowPlayingClaimUrl); + } + } + }); + + // display custom navigation menu + LinearLayoutManager llm = new LinearLayoutManager(this); + RecyclerView navItemsView = findViewById(R.id.nav_view_items); + navItemsView.setLayoutManager(llm); + navMenuAdapter = new NavigationMenuAdapter(flattenNavMenu(buildNavMenu(this)), this); + navMenuAdapter.setListener(new NavigationMenuAdapter.NavigationMenuItemClickListener() { + @Override + public void onNavigationMenuItemClicked(NavMenuItem menuItem) { + if (navMenuAdapter.getCurrentItemId() == menuItem.getId() && !Arrays.asList( + NavMenuItem.ID_ITEM_FOLLOWING, NavMenuItem.ID_ITEM_ALL_CONTENT, NavMenuItem.ID_ITEM_WALLET).contains(menuItem.getId())) { + // already open + navMenuAdapter.setCurrentItem(menuItem); + closeDrawer(); + return; + } + + navMenuAdapter.setCurrentItem(menuItem); + shouldOpenUserSelectedMenuItem = true; + selectedMenuItemId = menuItem.getId(); + closeDrawer(); + } + }); + navItemsView.setAdapter(navMenuAdapter); + + findViewById(R.id.sign_in_button_container).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + walletSyncSignIn(); + } + }); + } + + private void initSpecialRouteMap() { + specialRouteFragmentClassMap = new HashMap<>(); + specialRouteFragmentClassMap.put("about", AboutFragment.class); + specialRouteFragmentClassMap.put("allContent", AllContentFragment.class); + specialRouteFragmentClassMap.put("channels", ChannelManagerFragment.class); + specialRouteFragmentClassMap.put("invite", InvitesFragment.class); + specialRouteFragmentClassMap.put("invites", InvitesFragment.class); + specialRouteFragmentClassMap.put("library", LibraryFragment.class); + specialRouteFragmentClassMap.put("publish", PublishFragment.class); + specialRouteFragmentClassMap.put("publishes", PublishesFragment.class); + specialRouteFragmentClassMap.put("following", FollowingFragment.class); + specialRouteFragmentClassMap.put("rewards", RewardsFragment.class); + specialRouteFragmentClassMap.put("settings", SettingsFragment.class); + specialRouteFragmentClassMap.put("subscriptions", FollowingFragment.class); + specialRouteFragmentClassMap.put("wallet", WalletFragment.class); + specialRouteFragmentClassMap.put("discover", FollowingFragment.class); + } + + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + checkUrlIntent(intent); + checkNotificationOpenIntent(intent); + } + + public void addCameraPermissionListener(CameraPermissionListener listener) { + if (!cameraPermissionListeners.contains(listener)) { + cameraPermissionListeners.add(listener); + } + } + + public void removeCameraPermissionListener(CameraPermissionListener listener) { + cameraPermissionListeners.remove(listener); + } + + public void addDownloadActionListener(DownloadActionListener listener) { + if (!downloadActionListeners.contains(listener)) { + downloadActionListeners.add(listener); + } + } + + public void removeDownloadActionListener(DownloadActionListener listener) { + downloadActionListeners.remove(listener); + } + + public void addFilePickerListener(FilePickerListener listener) { + if (!filePickerListeners.contains(listener)) { + filePickerListeners.add(listener); + } + } + + public void removeFilePickerListener(FilePickerListener listener) { + filePickerListeners.remove(listener); + } + + public void addSdkStatusListener(SdkStatusListener listener) { + if (!sdkStatusListeners.contains(listener)) { + sdkStatusListeners.add(listener); + } + } + + public void removeSdkStatusListener(SdkStatusListener listener) { + sdkStatusListeners.remove(listener); + } + + public void addStoragePermissionListener(StoragePermissionListener listener) { + if (!storagePermissionListeners.contains(listener)) { + storagePermissionListeners.add(listener); + } + } + + public void removeStoragePermissionListener(StoragePermissionListener listener) { + storagePermissionListeners.remove(listener); + } + + public void addWalletBalanceListener(WalletBalanceListener listener) { + if (!walletBalanceListeners.contains(listener)) { + walletBalanceListeners.add(listener); + } + } + + public void removeWalletBalanceListener(WalletBalanceListener listener) { + walletBalanceListeners.remove(listener); + } + + public void removeNavFragment(Class fragmentClass, int navItemId) { + String key = buildNavFragmentKey(fragmentClass, navItemId, null); + if (openNavFragments.containsKey(key)) { + openNavFragments.remove(key); + } + } + + public void addFetchChannelsListener(FetchChannelsListener listener) { + if (!fetchChannelsListeners.contains(listener)) { + fetchChannelsListeners.add(listener); + } + } + public void removeFetchChannelsListener(FetchChannelsListener listener) { + fetchChannelsListeners.remove(listener); + } + + public void addFetchClaimsListener(FetchClaimsListener listener) { + if (!fetchClaimsListeners.contains(listener)) { + fetchClaimsListeners.add(listener); + } + } + public void removeFetchClaimsListener(FetchClaimsListener listener) { + fetchClaimsListeners.remove(listener); + } + + private void openSelectedMenuItem() { + switch (selectedMenuItemId) { + // TODO: reverse map lookup for class? + case NavMenuItem.ID_ITEM_FOLLOWING: + openFragment(FollowingFragment.class, true, NavMenuItem.ID_ITEM_FOLLOWING); + break; + case NavMenuItem.ID_ITEM_EDITORS_CHOICE: + openFragment(EditorsChoiceFragment.class, true, NavMenuItem.ID_ITEM_EDITORS_CHOICE); + break; + case NavMenuItem.ID_ITEM_ALL_CONTENT: + openFragment(AllContentFragment.class, true, NavMenuItem.ID_ITEM_ALL_CONTENT); + break; + + case NavMenuItem.ID_ITEM_NEW_PUBLISH: + openFragment(PublishFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH); + break; + case NavMenuItem.ID_ITEM_CHANNELS: + openFragment(ChannelManagerFragment.class, true, NavMenuItem.ID_ITEM_CHANNELS); + break; + case NavMenuItem.ID_ITEM_LIBRARY: + openFragment(LibraryFragment.class, true, NavMenuItem.ID_ITEM_LIBRARY); + break; + case NavMenuItem.ID_ITEM_PUBLISHES: + openFragment(PublishesFragment.class, true, NavMenuItem.ID_ITEM_PUBLISHES); + break; + + case NavMenuItem.ID_ITEM_WALLET: + openFragment(WalletFragment.class, true, NavMenuItem.ID_ITEM_WALLET); + break; + case NavMenuItem.ID_ITEM_REWARDS: + openFragment(RewardsFragment.class, true, NavMenuItem.ID_ITEM_REWARDS); + break; + case NavMenuItem.ID_ITEM_INVITES: + openFragment(InvitesFragment.class, true, NavMenuItem.ID_ITEM_INVITES); + break; + + case NavMenuItem.ID_ITEM_SETTINGS: + openFragment(SettingsFragment.class, true, NavMenuItem.ID_ITEM_SETTINGS); + break; + case NavMenuItem.ID_ITEM_ABOUT: + openFragment(AboutFragment.class, true, NavMenuItem.ID_ITEM_ABOUT); + break; + } + } + + public void openChannelClaim(Claim claim) { + Map params = new HashMap<>(); + params.put("url", !Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl()); + params.put("claim", getCachedClaimForUrl(claim.getPermanentUrl())); + openFragment(ChannelFragment.class, true, NavMenuItem.ID_ITEM_FOLLOWING, params); + } + + public void openChannelForm(Claim claim) { + Map params = new HashMap<>(); + if (claim != null) { + params.put("claim", claim); + } + openFragment(ChannelFormFragment.class, true, NavMenuItem.ID_ITEM_CHANNELS, params); + } + + public void openPublishesOnSuccessfulPublish() { + // close publish form + getSupportFragmentManager().popBackStack(); + openFragment(PublishesFragment.class, true, NavMenuItem.ID_ITEM_PUBLISHES); + } + + public void openPublishForm(Claim claim) { + Map params = new HashMap<>(); + if (claim != null) { + params.put("claim", claim); + } + openFragment(PublishFormFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH, params); + } + + public void openChannelUrl(String url) { + Map params = new HashMap<>(); + params.put("url", url); + params.put("claim", getCachedClaimForUrl(url)); + openFragment(ChannelFragment.class, true, NavMenuItem.ID_ITEM_FOLLOWING, params); + } + + private Claim getCachedClaimForUrl(String url) { + ClaimCacheKey key = new ClaimCacheKey(); + key.setUrl(url); + return Lbry.claimCache.containsKey(key) ? Lbry.claimCache.get(key) : null; + } + + public void setWunderbarValue(String value) { + EditText wunderbar = findViewById(R.id.wunderbar); + wunderbar.setText(value); + wunderbar.setSelection(0); + } + + public void openAllContentFragmentWithTag(String tag) { + Map params = new HashMap<>(); + params.put("singleTag", tag); + openFragment(AllContentFragment.class, true, NavMenuItem.ID_ITEM_ALL_CONTENT, params); + } + + public void openFileUrl(String url) { + Map params = new HashMap<>(); + params.put("url", url); + openFragment(FileViewFragment.class, true, NavMenuItem.ID_ITEM_FOLLOWING, params); + } + + public void openFileClaim(Claim claim) { + Map params = new HashMap<>(); + params.put("claimId", claim.getClaimId()); + params.put("url", !Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl()); + openFragment(FileViewFragment.class, true, NavMenuItem.ID_ITEM_FOLLOWING, params); + } + + private FragmentManager.OnBackStackChangedListener backStackChangedListener = new FragmentManager.OnBackStackChangedListener() { + @Override + public void onBackStackChanged() { + FragmentManager manager = getSupportFragmentManager(); + if (manager != null) { + Fragment currentFragment = getCurrentFragment(); + + } + } + }; + + public void setSelectedMenuItemForFragment(Fragment fragment) { + if (fragment != null) { + Class fragmentClass = fragment.getClass(); + if (fragmentClassNavIdMap.containsKey(fragmentClass)) { + navMenuAdapter.setCurrentItem(fragmentClassNavIdMap.get(fragmentClass)); + } + } + } + + private void renderPictureInPictureMode() { + findViewById(R.id.content_main).setVisibility(View.GONE); + findViewById(R.id.floating_balance_main_container).setVisibility(View.GONE); + findViewById(R.id.global_now_playing_card).setVisibility(View.GONE); + findViewById(R.id.global_sdk_initializing_status).setVisibility(View.GONE); + getSupportActionBar().hide(); + + PlayerView pipPlayer = findViewById(R.id.pip_player); + pipPlayer.setVisibility(View.VISIBLE); + pipPlayer.setPlayer(appPlayer); + pipPlayer.setUseController(false); + playerReassigned = true; + } + private void renderFullMode() { + getSupportActionBar().show(); + findViewById(R.id.content_main).setVisibility(View.VISIBLE); + findViewById(R.id.floating_balance_main_container).setVisibility(View.VISIBLE); + Fragment fragment = getCurrentFragment(); + if (!(fragment instanceof FileViewFragment)) { + findViewById(R.id.global_now_playing_card).setVisibility(View.VISIBLE); + } + if (!Lbry.SDK_READY) { + findViewById(R.id.global_sdk_initializing_status).setVisibility(View.VISIBLE); + } + + PlayerView pipPlayer = findViewById(R.id.pip_player); + pipPlayer.setVisibility(View.INVISIBLE); + pipPlayer.setPlayer(null); + playerReassigned = true; + } + + @Override + protected void onDestroy() { + unregisterReceivers(); + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); + if (receivedStopService || !isServiceRunning(this, LbrynetService.class)) { + notificationManager.cancelAll(); + } + if (dbHelper != null) { + dbHelper.close(); + } + if (mediaSession != null) { + mediaSession.release(); + } + stopExoplayer(); + nowPlayingClaim = null; + nowPlayingClaimUrl = null; + appStarted = false; + + if (!keepSdkBackground()) { + sendBroadcast(new Intent(LbrynetService.ACTION_STOP_SERVICE)); + } + + super.onDestroy(); + } + + public static void stopExoplayer() { + if (appPlayer != null) { + appPlayer.stop(true); + appPlayer.release(); + appPlayer = null; + } + if (playerCache != null) { + playerCache.release(); + playerCache = null; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + //getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + public void updateWalletBalance() { + WalletBalanceTask task = new WalletBalanceTask(new WalletBalanceTask.WalletBalanceHandler() { + @Override + public void onSuccess(WalletBalance walletBalance) { + for (WalletBalanceListener listener : walletBalanceListeners) { + if (listener != null) { + listener.onWalletBalanceUpdated(walletBalance); + } + } + Lbry.walletBalance = walletBalance; + updateFloatingWalletBalance(); + updateUsdWalletBalanceInNav(); + + sendBroadcast(new Intent(ACTION_WALLET_BALANCE_UPDATED)); + } + + @Override + public void onError(Exception error) { + + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + @Override + protected void onResume() { + super.onResume(); + + applyNavbarSigninPadding(); + checkFirstRun(); + checkNowPlaying(); + fetchRewards(); + + // check (and start) the LBRY SDK service serviceRunning = isServiceRunning(this, LbrynetService.class); if (!serviceRunning) { - CurrentLaunchTiming.setColdStart(true); + Lbry.SDK_READY = false; + findViewById(R.id.global_sdk_initializing_status).setVisibility(View.VISIBLE); ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice"); } checkSdkReady(); + showSignedInUser(); + checkPendingOpens(); - checkNotificationOpenIntent(getIntent()); + if (Lbry.SDK_READY) { + findViewById(R.id.global_sdk_initializing_status).setVisibility(View.GONE); + } + } - mReactRootView = new RNGestureHandlerEnabledRootView(this); - mReactInstanceManager = ReactInstanceManager.builder() - .setApplication(getApplication()) - .setCurrentActivity(this) - .setBundleAssetName("index.android.bundle") - .setJSMainModulePath("index") - .addPackage(new MainReactPackage()) - .addPackage(new AsyncStoragePackage()) - .addPackage(new FastImageViewPackage()) - .addPackage(new RNCWebViewPackage()) - .addPackage(new ReactVideoPackage()) - .addPackage(new ReanimatedPackage()) - .addPackage(new RNCameraPackage()) - .addPackage(new RNFetchBlobPackage()) - .addPackage(new RNFSPackage()) - .addPackage(new RNGestureHandlerPackage()) - .addPackage(new SnackbarPackage()) - .addPackage(new LbryReactPackage()) - .setUseDeveloperSupport(BuildConfig.DEBUG) - .setInitialLifecycleState(LifecycleState.RESUMED) - .build(); - mReactRootView.startReactApplication(mReactInstanceManager, "LBRYApp", null); + private void checkPendingOpens() { + if (pendingFollowingReload) { + loadFollowingContent(); + pendingFollowingReload = false; + } + if (!Helper.isNullOrEmpty(pendingAllContentTag)) { + openAllContentFragmentWithTag(pendingAllContentTag); + pendingAllContentTag = null; + } else if (!Helper.isNullOrEmpty(pendingChannelUrl)) { + openChannelUrl(pendingChannelUrl); + pendingChannelUrl = null; + } else if (pendingOpenWalletPage) { + openFragment(WalletFragment.class, true, NavMenuItem.ID_ITEM_WALLET); + } else if (pendingOpenRewardsPage) { + openFragment(RewardsFragment.class, true, NavMenuItem.ID_ITEM_REWARDS); + } + } - registerNotificationsReceiver(); + @Override + protected void onPause() { + if (!enteringPIPMode && appPlayer != null) { + appPlayer.setPlayWhenReady(false); + } + super.onPause(); + } - setContentView(mReactRootView); + public static void suspendGlobalPlayer(Context context) { + if (MainActivity.appPlayer != null) { + MainActivity.appPlayer.setPlayWhenReady(false); + } + if (context instanceof MainActivity) { + ((MainActivity) context).hideGlobalNowPlaying(); + } + } + public static void resumeGlobalPlayer(Context context) { + if (context instanceof MainActivity) { + ((MainActivity) context).checkNowPlaying(); + } + } + + private void toggleUrlSuggestions(boolean visible) { + View container = findViewById(R.id.url_suggestions_container); + View closeIcon = findViewById(R.id.wunderbar_close); + EditText wunderbar = findViewById(R.id.wunderbar); + wunderbar.setPadding(0, 0, visible ? getScaledValue(36) : 0, 0); + + container.setVisibility(visible ? View.VISIBLE : View.GONE); + closeIcon.setVisibility(visible ? View.VISIBLE : View.GONE); + } + + public int getScaledValue(int value) { + float scale = getResources().getDisplayMetrics().density; + return Helper.getScaledValue(value, scale); + } + + + public boolean canShowUrlSuggestions() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + return sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_URL_SUGGESTIONS, false); + } + + public boolean keepSdkBackground() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + return sp.getBoolean(MainActivity.PREFERENCE_KEY_KEEP_SDK_BACKGROUND, true); + } + + private void setupUriBar() { + findViewById(R.id.wunderbar_close).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + clearWunderbarFocus(view); + } + }); + + EditText wunderbar = findViewById(R.id.wunderbar); + wunderbar.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + if (hasFocus) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(view, 0); + } + + if (canShowUrlSuggestions()) { + toggleUrlSuggestions(hasFocus); + if (hasFocus && Helper.isNullOrEmpty(Helper.getValue(((EditText) view).getText()))) { + displayUrlSuggestionsForNoInput(); + } + } + } + }); + + wunderbar.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + if (charSequence != null && canShowUrlSuggestions()) { + handleUriInputChanged(charSequence.toString().trim()); + } + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + wunderbar.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { + if (actionId == EditorInfo.IME_ACTION_GO) { + String input = Helper.getValue(wunderbar.getText()); + boolean handled = false; + if (input.startsWith(LbryUri.PROTO_DEFAULT) && !input.equalsIgnoreCase(LbryUri.PROTO_DEFAULT)) { + try { + LbryUri uri = LbryUri.parse(input); + if (uri.isChannel()) { + openChannelUrl(uri.toString()); + clearWunderbarFocus(wunderbar); + handled = true; + } else { + openFileUrl(uri.toString()); + clearWunderbarFocus(wunderbar); + handled = true; + } + } catch (LbryUriException ex) { + // pass + } + } + if (!handled) { + // search + launchSearch(input); + clearWunderbarFocus(wunderbar); + } + + return true; + } + + return false; + } + }); + + urlSuggestionListAdapter = new UrlSuggestionListAdapter(this); + urlSuggestionListAdapter.setListener(new UrlSuggestionListAdapter.UrlSuggestionClickListener() { + @Override + public void onUrlSuggestionClicked(UrlSuggestion urlSuggestion) { + switch (urlSuggestion.getType()) { + case UrlSuggestion.TYPE_CHANNEL: + // open channel page + if (urlSuggestion.getClaim() != null) { + openChannelClaim(urlSuggestion.getClaim()); + } else { + openChannelUrl(urlSuggestion.getUri().toString()); + } + break; + case UrlSuggestion.TYPE_FILE: + if (urlSuggestion.getClaim() != null) { + openFileClaim(urlSuggestion.getClaim()); + } else { + openFileUrl(urlSuggestion.getUri().toString()); + } + break; + case UrlSuggestion.TYPE_SEARCH: + launchSearch(urlSuggestion.getText()); + break; + case UrlSuggestion.TYPE_TAG: + // open tag page + openAllContentFragmentWithTag(urlSuggestion.getText()); + break; + } + clearWunderbarFocus(findViewById(R.id.wunderbar)); + } + }); + + RecyclerView urlSuggestionList = findViewById(R.id.url_suggestions); + LinearLayoutManager llm = new LinearLayoutManager(this); + urlSuggestionList.setLayoutManager(llm); + urlSuggestionList.setAdapter(urlSuggestionListAdapter); + } + + public void clearWunderbarFocus(View view) { + findViewById(R.id.wunderbar).clearFocus(); + findViewById(R.id.app_bar_main_container).requestFocus(); + InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + public View getWunderbar() { + return findViewById(R.id.wunderbar); + } + + private void launchSearch(String text) { + Fragment currentFragment = getCurrentFragment(); + if (currentFragment instanceof SearchFragment) { + ((SearchFragment) currentFragment).search(text, 0); + } else { + try { + SearchFragment fragment = SearchFragment.class.newInstance(); + fragment.setCurrentQuery(text); + openFragment(fragment, true); + } catch (Exception ex) { + // pass + } + } + } + + private void resolveUrlSuggestions(List urls) { + ResolveTask task = new ResolveTask(urls, Lbry.LBRY_TV_CONNECTION_STRING, null, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + if (findViewById(R.id.url_suggestions_container).getVisibility() == View.VISIBLE) { + for (int i = 0; i < claims.size(); i++) { + // build a simple url from the claim for matching + Claim claim = claims.get(i); + Claim actualClaim = claim; + boolean isRepost = false; + if (Claim.TYPE_REPOST.equalsIgnoreCase(claim.getValueType())) { + actualClaim = claim.getRepostedClaim(); + isRepost = true; + } + if (Helper.isNullOrEmpty(claim.getName())) { + continue; + } + + LbryUri simpleUrl = new LbryUri(); + if (actualClaim.getName().startsWith("@") && !isRepost) { + // channel + simpleUrl.setChannelName(actualClaim.getName()); + } else { + simpleUrl.setStreamName(claim.getName()); + } + + urlSuggestionListAdapter.setClaimForUrl(simpleUrl, actualClaim); + } + urlSuggestionListAdapter.notifyDataSetChanged(); + } + } + + @Override + public void onError(Exception error) { + + } + }); + task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); + } + + private void displayUrlSuggestionsForNoInput() { + urlSuggestionListAdapter.clear(); + loadDefaultSuggestionsForBlankUrl(); + } + + private void handleUriInputChanged(String text) { + // build the default suggestions + urlSuggestionListAdapter.clear(); + if (Helper.isNullOrEmpty(text) || text.trim().equals("@")) { + displayUrlSuggestionsForNoInput(); + return; + } + + List defaultSuggestions = buildDefaultSuggestions(text); + urlSuggestionListAdapter.addUrlSuggestions(defaultSuggestions); + if (LbryUri.PROTO_DEFAULT.equalsIgnoreCase(text)) { + return; + } + + LighthouseAutoCompleteTask task = new LighthouseAutoCompleteTask(text, null, new LighthouseAutoCompleteTask.AutoCompleteResultHandler() { + @Override + public void onSuccess(List suggestions) { + String wunderBarText = Helper.getValue(((EditText) findViewById(R.id.wunderbar)).getText()); + if (wunderBarText.equalsIgnoreCase(text)) { + urlSuggestionListAdapter.addUrlSuggestions(suggestions); + List urls = urlSuggestionListAdapter.getItemUrls(); + resolveUrlSuggestions(urls); + } + } + + @Override + public void onError(Exception error) { + + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void loadDefaultSuggestionsForBlankUrl() { + if (recentUrlHistory != null && recentUrlHistory.size() > 0) { + urlSuggestionListAdapter.addUrlSuggestions(recentUrlHistory); + } + + FetchRecentUrlHistoryTask task = new FetchRecentUrlHistoryTask(DatabaseHelper.getInstance(), new FetchRecentUrlHistoryTask.FetchRecentUrlHistoryHandler() { + @Override + public void onSuccess(List recentHistory) { + List suggestions = new ArrayList<>(recentHistory); + List lbrySuggestions = buildLbryUrlSuggestions(); + if (suggestions.size() < 10) { + for (int i = suggestions.size(), j = 0; i < 10 && j < lbrySuggestions.size(); i++, j++) { + suggestions.add(lbrySuggestions.get(j)); + } + } else if (suggestions.size() == 0) { + suggestions.addAll(lbrySuggestions); + } + + for (UrlSuggestion suggestion : suggestions) { + suggestion.setUseTextAsDescription(true); + } + + recentUrlHistory = new ArrayList<>(suggestions); + urlSuggestionListAdapter.clear(); + urlSuggestionListAdapter.addUrlSuggestions(recentUrlHistory); + List urls = urlSuggestionListAdapter.getItemUrls(); + resolveUrlSuggestions(urls); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private List buildLbryUrlSuggestions() { + List suggestions = new ArrayList<>(); + suggestions.add(new UrlSuggestion( + UrlSuggestion.TYPE_FILE, "What is LBRY?", LbryUri.tryParse("lbry://what#19b9c243bea0c45175e6a6027911abbad53e983e"))); + suggestions.add(new UrlSuggestion( + UrlSuggestion.TYPE_CHANNEL, "LBRYCast", LbryUri.tryParse("lbry://@lbrycast#4c29f8b013adea4d5cca1861fb2161d5089613ea"))); + suggestions.add(new UrlSuggestion( + UrlSuggestion.TYPE_CHANNEL, "The LBRY Channel", LbryUri.tryParse("lbry://@lbry#3fda836a92faaceedfe398225fb9b2ee2ed1f01a"))); + for (UrlSuggestion suggestion : suggestions) { + suggestion.setUseTextAsDescription(true); + } + return suggestions; + } + + private List buildDefaultSuggestions(String text) { + List suggestions = new ArrayList(); + + if (LbryUri.PROTO_DEFAULT.equalsIgnoreCase(text)) { + loadDefaultSuggestionsForBlankUrl(); + return recentUrlHistory != null ? recentUrlHistory : new ArrayList<>(); + } + + // First item is always search + if (!text.startsWith(LbryUri.PROTO_DEFAULT)) { + UrlSuggestion searchSuggestion = new UrlSuggestion(UrlSuggestion.TYPE_SEARCH, text); + suggestions.add(searchSuggestion); + } + + if (!text.matches(LbryUri.REGEX_INVALID_URI)) { + boolean isUrlWithScheme = text.startsWith(LbryUri.PROTO_DEFAULT); + boolean isChannel = text.startsWith("@"); + LbryUri uri = null; + if (isUrlWithScheme && text.length() > 7) { + try { + uri = LbryUri.parse(text); + isChannel = uri.isChannel(); + } catch (LbryUriException ex) { + // pass + } + } + + if (!isChannel) { + if (uri == null) { + uri = new LbryUri(); + uri.setStreamName(text); + } + UrlSuggestion fileSuggestion = new UrlSuggestion(UrlSuggestion.TYPE_FILE, text); + fileSuggestion.setUri(uri); + suggestions.add(fileSuggestion); + } + + if (text.indexOf(' ') == -1) { + // channels should not contain spaces + if (isChannel) { + if (uri == null) { + uri = new LbryUri(); + uri.setChannelName(text); + } + UrlSuggestion suggestion = new UrlSuggestion(UrlSuggestion.TYPE_CHANNEL, text); + suggestion.setUri(uri); + suggestions.add(suggestion); + } + } + + if (!isUrlWithScheme && !isChannel) { + UrlSuggestion suggestion = new UrlSuggestion(UrlSuggestion.TYPE_TAG, text); + suggestions.add(suggestion); + } + } + + return suggestions; + } + + public void checkNowPlaying() { + Fragment fragment = getCurrentFragment(); + if (fragment instanceof FileViewFragment) { + return; + } + + if (nowPlayingClaim != null) { + findViewById(R.id.global_now_playing_card).setVisibility(View.VISIBLE); + ((TextView) findViewById(R.id.global_now_playing_title)).setText(nowPlayingClaim.getTitle()); + ((TextView) findViewById(R.id.global_now_playing_channel_title)).setText(nowPlayingClaim.getPublisherTitle()); + } + if (appPlayer != null) { + PlayerView playerView = findViewById(R.id.global_now_playing_player_view); + playerView.setPlayer(null); + playerView.setPlayer(appPlayer); + playerView.setUseController(false); + playerReassigned = true; + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + + public void hideGlobalNowPlaying() { + findViewById(R.id.global_now_playing_card).setVisibility(View.GONE); + } + + public void unsetFitsSystemWindows(View view) { + view.setFitsSystemWindows(false); + } + + public void enterFullScreenMode() { + hideFloatingWalletBalance(); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + findViewById(R.id.global_sdk_initializing_status).setVisibility(View.GONE); + findViewById(R.id.app_bar_main_container).setFitsSystemWindows(false); + + View decorView = getWindow().getDecorView(); + decorView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | + View.SYSTEM_UI_FLAG_FULLSCREEN | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + } + + public int getStatusBarHeight() { + int height = 0; + int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + height = getResources().getDimensionPixelSize(resourceId); + } + return height; + } + + public void exitFullScreenMode() { + View appBarMainContainer = findViewById(R.id.app_bar_main_container); + View decorView = getWindow().getDecorView(); + int flags = isDarkMode() ? (View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE) : + (View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE); + appBarMainContainer.setFitsSystemWindows(true); + decorView.setSystemUiVisibility(flags); + + if (!Lbry.SDK_READY) { + findViewById(R.id.global_sdk_initializing_status).setVisibility(View.VISIBLE); + } + showFloatingWalletBalance(); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.show(); + } + } + + private void initKeyStore() { + try { + Lbry.KEYSTORE = Utils.initKeyStore(this); + } catch (Exception ex) { + // This shouldn't happen, but in case it does. + Toast.makeText(this, "The keystore could not be initialized. The app requires a secure keystore to run properly.", Toast.LENGTH_LONG).show(); + finish(); + } + } + + private void checkFirstRun() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + boolean firstRunCompleted = sp.getBoolean(PREFERENCE_KEY_INTERNAL_FIRST_RUN_COMPLETED, false); + if (!firstRunCompleted) { + startActivity(new Intent(this, FirstRunActivity.class)); + } else if (!appStarted) { + // first run completed, startup + startup(); + return; + } + + if (getSupportFragmentManager().getBackStackEntryCount() == 0) { + openFragment(FollowingFragment.class, false, NavMenuItem.ID_ITEM_FOLLOWING); + } + } + + public static boolean isServiceRunning(Context context, Class serviceClass) { + ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) { + if (serviceClass.getName().equals(serviceInfo.service.getClassName())) { + return true; + } + } + + return false; + } + + private void loadAuthToken() { + // Check if an auth token is present and then set it for Lbryio + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + String encryptedAuthToken = sp.getString(PREFERENCE_KEY_AUTH_TOKEN, null); + if (!Helper.isNullOrEmpty(encryptedAuthToken)) { + try { + Lbryio.AUTH_TOKEN = new String(Utils.decrypt( + Base64.decode(encryptedAuthToken, Base64.NO_WRAP), this, Lbry.KEYSTORE), "UTF8"); + } catch (Exception ex) { + // pass. A new auth token would have to be generated if the old one cannot be decrypted + Log.e(TAG, "Could not decrypt existing auth token.", ex); + } + } } private void checkSdkReady() { - if (!lbrySdkReady) { + if (!Lbry.SDK_READY) { + new Handler().postDelayed(() -> { + if (checkSdkReadyTask != null && checkSdkReadyTask.getStatus() != AsyncTask.Status.FINISHED) { + // task already running + return; + } + checkSdkReadyTask = new CheckSdkReadyTask(MainActivity.this, sdkStatusListeners); + checkSdkReadyTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + }, CHECK_SDK_READY_INTERVAL); + } else { + scheduleWalletBalanceUpdate(); + scheduleWalletSyncTask(); + initFloatingWalletBalance(); + } + } + + public void onSdkReady() { + if (Lbryio.isSignedIn()) { + checkSyncedWallet(); + } + + findViewById(R.id.global_sdk_initializing_status).setVisibility(View.GONE); + + syncWalletAndLoadPreferences(); + scheduleWalletBalanceUpdate(); + scheduleWalletSyncTask(); + fetchOwnChannels(); + fetchOwnClaims(); + + initFloatingWalletBalance(); + + checkAndClaimNewAndroidReward(); + } + + public void checkAndClaimNewAndroidReward() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + boolean rewardClaimed = sp.getBoolean(PREFERENCE_KEY_INTERNAL_NEW_ANDROID_REWARD_CLAIMED, false); + if (!rewardClaimed) { + ClaimRewardTask task = new ClaimRewardTask( + Reward.TYPE_NEW_ANDROID, + null, + null, + this, + new ClaimRewardTask.ClaimRewardHandler() { + @Override + public void onSuccess(double amountClaimed, String message) { + if (Helper.isNullOrEmpty(message)) { + message = getResources().getQuantityString( + R.plurals.claim_reward_message, + amountClaimed == 1 ? 1 : 2, + new DecimalFormat(Helper.LBC_CURRENCY_FORMAT_PATTERN).format(amountClaimed)); + } + Snackbar.make(findViewById(R.id.content_main), message, Snackbar.LENGTH_LONG).show(); + if (sp != null) { + sp.edit().putBoolean(PREFERENCE_KEY_INTERNAL_NEW_ANDROID_REWARD_CLAIMED, true).apply(); + } + } + + @Override + public void onError(Exception error) { + // pass. fail silently + if (sp != null) { + sp.edit().putBoolean(PREFERENCE_KEY_INTERNAL_NEW_ANDROID_REWARD_CLAIMED, true).apply(); + } + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + public void initMediaSession() { + ComponentName mediaButtonReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class); + mediaSession = new MediaSessionCompat(getApplicationContext(), "LBRYMediaSession", mediaButtonReceiver, null); + MediaSessionConnector connector = new MediaSessionConnector(mediaSession); + connector.setPlayer(MainActivity.appPlayer); + mediaSession.setActive(true); + } + + public void showFloatingWalletBalance() { + findViewById(R.id.floating_balance_main_container).setVisibility(View.VISIBLE); + } + public void hideFloatingWalletBalance() { + findViewById(R.id.floating_balance_main_container).setVisibility(View.GONE); + } + public void hideFloatingRewardsValue() { + findViewById(R.id.floating_reward_container).setVisibility(View.INVISIBLE); + } + + private void initFloatingWalletBalance() { + findViewById(R.id.floating_balance_container).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openFragment(WalletFragment.class, true, NavMenuItem.ID_ITEM_WALLET); + } + }); + findViewById(R.id.floating_reward_container).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openFragment(RewardsFragment.class, true, NavMenuItem.ID_ITEM_REWARDS); + } + }); + } + + private void updateUsdWalletBalanceInNav() { + double usdBalance = Lbry.walletBalance.getAvailable().doubleValue() * Lbryio.LBCUSDRate; + if (navMenuAdapter != null) { + navMenuAdapter.setExtraLabelForItem( + NavMenuItem.ID_ITEM_WALLET, + Lbryio.LBCUSDRate > 0 ? String.format("$%s", Helper.SIMPLE_CURRENCY_FORMAT.format(usdBalance)) : null + ); + } + } + + private void updateFloatingWalletBalance() { + if (!hasLoadedFirstBalance) { + findViewById(R.id.floating_balance_loading).setVisibility(View.GONE); + findViewById(R.id.floating_balance_value).setVisibility(View.VISIBLE); + hasLoadedFirstBalance = true; + } + + ((TextView) findViewById(R.id.floating_balance_value)).setText(Helper.shortCurrencyFormat( + Lbry.walletBalance == null ? 0 : Lbry.walletBalance.getAvailable().doubleValue())); + } + + private void scheduleWalletBalanceUpdate() { + if (scheduler != null && !walletBalanceUpdateScheduled) { + scheduler.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + updateWalletBalance(); + } + }, 0, 5, TimeUnit.SECONDS); + walletBalanceUpdateScheduled = true; + } + } + + private void scheduleWalletSyncTask() { + if (scheduler != null && !walletSyncScheduled) { + scheduler.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + syncWalletAndLoadPreferences(); + } + }, 0, 5, TimeUnit.MINUTES); + walletSyncScheduled = true; + } + } + + public void saveSharedUserState() { + if (!userSyncEnabled()) { + return; + } + SaveSharedUserStateTask saveTask = new SaveSharedUserStateTask(new SaveSharedUserStateTask.SaveSharedUserStateHandler() { + @Override + public void onSuccess() { + // push wallet sync changes + pushCurrentWalletSync(); + } + + @Override + public void onError(Exception error) { + // pass + } + }); + saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void loadSharedUserState() { + // load wallet preferences + LoadSharedUserStateTask loadTask = new LoadSharedUserStateTask(MainActivity.this, new LoadSharedUserStateTask.LoadSharedUserStateHandler() { + @Override + public void onSuccess(List subscriptions, List followedTags) { + if (subscriptions != null && subscriptions.size() > 0) { + // reload subscriptions if wallet fragment is FollowingFragment + //openNavFragments.get + MergeSubscriptionsTask mergeTask = new MergeSubscriptionsTask( + subscriptions, MainActivity.this, new MergeSubscriptionsTask.MergeSubscriptionsHandler() { + @Override + public void onSuccess(List subscriptions, List diff) { + Lbryio.subscriptions = new ArrayList<>(subscriptions); + if (diff != null && diff.size() > 0) { + saveSharedUserState(); + } + for (Fragment fragment : openNavFragments.values()) { + if (fragment instanceof FollowingFragment) { + // reload local subscriptions + ((FollowingFragment) fragment).fetchLoadedSubscriptions(true); + } + } + } + + @Override + public void onError(Exception error) { + Log.e(TAG, String.format("merge subscriptions failed: %s", error.getMessage()), error); + } + }); + mergeTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + if (followedTags != null && followedTags.size() > 0) { + List previousTags = new ArrayList<>(Lbry.followedTags); + Lbry.followedTags = new ArrayList<>(followedTags); + for (Fragment fragment : openNavFragments.values()) { + if (fragment instanceof AllContentFragment) { + AllContentFragment acFragment = (AllContentFragment) fragment; + if (!acFragment.isSingleTagView() && + acFragment.getCurrentContentScope() == ContentScopeDialogFragment.ITEM_TAGS && + !previousTags.equals(followedTags)) { + acFragment.fetchClaimSearchContent(true); + } + } + } + } + } + + @Override + public void onError(Exception error) { + Log.e(TAG, String.format("load shared user state failed: %s", error != null ? error.getMessage() : "no error message"), error); + } + }); + loadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void pushCurrentWalletSync() { + String password = Utils.getSecureValue(SECURE_VALUE_KEY_SAVED_PASSWORD, this, Lbry.KEYSTORE); + SyncApplyTask fetchTask = new SyncApplyTask(true, password, new DefaultSyncTaskHandler() { + @Override + public void onSyncApplySuccess(String hash, String data) { + SyncSetTask setTask = new SyncSetTask(Lbryio.lastRemoteHash, hash, data, null); + setTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + @Override + public void onSyncApplyError(Exception error) { } + }); + fetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private boolean userSyncEnabled() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + boolean walletSyncEnabled = sp.getBoolean(PREFERENCE_KEY_INTERNAL_WALLET_SYNC_ENABLED, false); + return walletSyncEnabled && Lbryio.isSignedIn(); + } + + public void syncSet(String hash, String data) { + if (syncSetTask == null || syncSetTask.getStatus() == AsyncTask.Status.FINISHED) { + syncSetTask = new SyncSetTask(Lbryio.lastRemoteHash, hash, data, new DefaultSyncTaskHandler() { + @Override + public void onSyncSetSuccess(String hash) { + Lbryio.lastRemoteHash = hash; + WalletSync walletSync = new WalletSync(hash, data); + Lbryio.lastWalletSync = walletSync; + + if (pendingSyncSetQueue.size() > 0) { + fullSyncInProgress = true; + WalletSync nextSync = pendingSyncSetQueue.remove(0); + syncSet(nextSync.getHash(), nextSync.getData()); + } else if (queuedSyncCount > 0) { + queuedSyncCount--; + syncApplyAndSet(); + } + + fullSyncInProgress = false; + } + @Override + public void onSyncSetError(Exception error) { + // log app exceptions + if (pendingSyncSetQueue.size() > 0) { + WalletSync nextSync = pendingSyncSetQueue.remove(0); + syncSet(nextSync.getHash(), nextSync.getData()); + } else if (queuedSyncCount > 0) { + queuedSyncCount--; + syncApplyAndSet(); + } + + fullSyncInProgress = false; + } + }); + syncSetTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + WalletSync pending = new WalletSync(hash, data); + pendingSyncSetQueue.add(pending); + } + } + + public void syncApplyAndSet() { + fullSyncInProgress = true; + String password = Utils.getSecureValue(SECURE_VALUE_KEY_SAVED_PASSWORD, this, Lbry.KEYSTORE); + SyncApplyTask fetchTask = new SyncApplyTask(true, password, new DefaultSyncTaskHandler() { + @Override + public void onSyncApplySuccess(String hash, String data) { + if (!hash.equalsIgnoreCase(Lbryio.lastRemoteHash)) { + syncSet(hash, data); + } else { + fullSyncInProgress = false; + queuedSyncCount = 0; + } + } + @Override + public void onSyncApplyError(Exception error) { + fullSyncInProgress = false; + if (queuedSyncCount > 0) { + queuedSyncCount--; + syncApplyAndSet(); + } + } + }); + fetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void syncWalletAndLoadPreferences() { + if (!userSyncEnabled()) { + return; + } + if (fullSyncInProgress) { + queuedSyncCount++; + } + + fullSyncInProgress = true; + String password = Utils.getSecureValue(SECURE_VALUE_KEY_SAVED_PASSWORD, this, Lbry.KEYSTORE); + SyncGetTask task = new SyncGetTask(password, true, null, new DefaultSyncTaskHandler() { + @Override + public void onSyncGetSuccess(WalletSync walletSync) { + Lbryio.lastWalletSync = walletSync; + Lbryio.lastRemoteHash = walletSync.getHash(); + loadSharedUserState(); + } + + @Override + public void onSyncGetWalletNotFound() { + // pass. This actually shouldn't happen at this point. + // But if it does, send what we have + if (Lbryio.isSignedIn() && userSyncEnabled()) { + syncApplyAndSet(); + } + } + + @Override + public void onSyncGetError(Exception error) { + // pass + Log.e(TAG, String.format("sync get failed: %s", error != null ? error.getMessage() : "no error message"), error); + + fullSyncInProgress = false; + if (queuedSyncCount > 0) { + queuedSyncCount--; + syncApplyAndSet(); + } + } + + @Override + public void onSyncApplySuccess(String hash, String data) { + if (!hash.equalsIgnoreCase(Lbryio.lastRemoteHash)) { + syncSet(hash, data); + } else { + fullSyncInProgress = false; + queuedSyncCount = 0; + } + + loadSharedUserState(); + } + + @Override + public void onSyncApplyError(Exception error) { + // pass + Log.e(TAG, String.format("sync apply failed: %s", error != null ? error.getMessage() : "no error message"), error); + fullSyncInProgress = false; + if (queuedSyncCount > 0) { + queuedSyncCount--; + syncApplyAndSet(); + } + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void registerRequestsReceiver() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ACTION_AUTH_TOKEN_GENERATED); + intentFilter.addAction(ACTION_USER_SIGN_IN_SUCCESS); + intentFilter.addAction(ACTION_OPEN_ALL_CONTENT_TAG); + intentFilter.addAction(ACTION_OPEN_CHANNEL_URL); + intentFilter.addAction(ACTION_OPEN_WALLET_PAGE); + intentFilter.addAction(ACTION_OPEN_REWARDS_PAGE); + intentFilter.addAction(ACTION_SAVE_SHARED_USER_STATE); + requestsReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (ACTION_AUTH_TOKEN_GENERATED.equalsIgnoreCase(action)) { + handleAuthTokenGenerated(intent); + } else if (ACTION_USER_SIGN_IN_SUCCESS.equalsIgnoreCase(action)) { + handleUserSignInSuccess(intent); + } else if (ACTION_OPEN_ALL_CONTENT_TAG.equalsIgnoreCase(action)) { + handleOpenContentTag(intent); + } else if (ACTION_OPEN_CHANNEL_URL.equalsIgnoreCase(action)) { + handleOpenChannelUrl(intent); + } else if (ACTION_OPEN_WALLET_PAGE.equalsIgnoreCase(action)) { + pendingOpenWalletPage = true; + } else if (ACTION_OPEN_REWARDS_PAGE.equalsIgnoreCase(action)) { + pendingOpenRewardsPage = true; + } else if (ACTION_SAVE_SHARED_USER_STATE.equalsIgnoreCase(action)) { + saveSharedUserState(); + } + } + + private void handleAuthTokenGenerated(Intent intent) { + // store the value + String encryptedAuthToken = intent.getStringExtra("authToken"); + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(MainActivity.this); + sp.edit().putString(PREFERENCE_KEY_AUTH_TOKEN, encryptedAuthToken).apply(); + } + + private void handleOpenContentTag(Intent intent) { + String tag = intent.getStringExtra("tag"); + if (!Helper.isNullOrEmpty(tag)) { + pendingAllContentTag = tag; + } + } + private void handleUserSignInSuccess(Intent intent) { + pendingFollowingReload = true; + } + private void handleOpenChannelUrl(Intent intent) { + String url = intent.getStringExtra("url"); + pendingChannelUrl = url; + } + }; + registerReceiver(requestsReceiver, intentFilter); + } + + private void loadFollowingContent() { + for (Fragment fragment : openNavFragments.values()) { + if (fragment instanceof FollowingFragment) { + ((FollowingFragment) fragment).loadFollowing(); + } + } + } + public void showMessage(int stringResourceId) { + Snackbar.make(findViewById(R.id.content_main), stringResourceId, Snackbar.LENGTH_LONG).show(); + } + public void showMessage(String message) { + Snackbar.make(findViewById(R.id.content_main), message, Snackbar.LENGTH_LONG).show(); + } + public void showError(String message) { + Snackbar.make(findViewById(R.id.content_main), message, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } + + @Override + public void onBackPressed() { + + if (findViewById(R.id.url_suggestions_container).getVisibility() == View.VISIBLE) { + clearWunderbarFocus(findViewById(R.id.wunderbar)); + return; + } + if (backPressInterceptor != null && backPressInterceptor.onBackPressed()) { + return; + } + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + if (drawer.isDrawerOpen(GravityCompat.START)) { + drawer.closeDrawer(GravityCompat.START); + } else { + boolean handled = false; + // TODO: Refactor both forms as back press interceptors? + ChannelFormFragment channelFormFragment = null; + PublishFormFragment publishFormFragment = null; + for (Fragment fragment : openNavFragments.values()) { + if (fragment instanceof ChannelFormFragment) { + channelFormFragment = ((ChannelFormFragment) fragment); + break; + } + if (fragment instanceof PublishFormFragment) { + publishFormFragment = ((PublishFormFragment) fragment); + break; + } + } + if (channelFormFragment != null && channelFormFragment.isSaveInProgress()) { + handled = true; + return; + } + if (publishFormFragment != null && (publishFormFragment.isSaveInProgress() || publishFormFragment.isTranscodeInProgress())) { + if (publishFormFragment.isTranscodeInProgress()) { + showMessage(R.string.transcode_in_progress); + } + handled = true; + return; + } + + if (!handled) { + // check fragment and nav history + FragmentManager manager = getSupportFragmentManager(); + int backCount = getSupportFragmentManager().getBackStackEntryCount(); + if (backCount > 0) { + // we can pop the stack + manager.popBackStack(); + setSelectedNavMenuItemForFragment(getCurrentFragment()); + } else if (!enterPIPMode()) { + // we're at the top of the stack + moveTaskToBack(true); + return; + } + } + } + } + + public void simpleSignIn() { + Intent intent = new Intent(this, VerificationActivity.class); + intent.putExtra("flow", VerificationActivity.VERIFICATION_FLOW_SIGN_IN); + startActivityForResult(intent, REQUEST_SIMPLE_SIGN_IN); + } + + public void walletSyncSignIn() { + Intent intent = new Intent(this, VerificationActivity.class); + intent.putExtra("flow", VerificationActivity.VERIFICATION_FLOW_WALLET); + startActivityForResult(intent, REQUEST_WALLET_SYNC_SIGN_IN); + } + + public void rewardsSignIn() { + Intent intent = new Intent(this, VerificationActivity.class); + intent.putExtra("flow", VerificationActivity.VERIFICATION_FLOW_REWARDS); + startActivityForResult(intent, REQUEST_REWARDS_VERIFY_SIGN_IN); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + switch (requestCode) { + case REQUEST_STORAGE_PERMISSION: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + for (StoragePermissionListener listener : storagePermissionListeners) { + listener.onStoragePermissionGranted(); + } + } else { + for (StoragePermissionListener listener : storagePermissionListeners) { + listener.onStoragePermissionRefused(); + } + } + startingPermissionRequest = false; + break; + + case REQUEST_CAMERA_PERMISSION: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + for (CameraPermissionListener listener : cameraPermissionListeners) { + listener.onCameraPermissionGranted(); + } + } else { + for (CameraPermissionListener listener : cameraPermissionListeners) { + listener.onCameraPermissionRefused(); + } + } + startingPermissionRequest = false; + break; + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_FILE_PICKER) { + startingFilePickerActivity = false; + if (resultCode == RESULT_OK) { + Uri fileUri = data.getData(); + String filePath = Helper.getRealPathFromURI_API19(this, fileUri); + for (FilePickerListener listener : filePickerListeners) { + listener.onFilePicked(filePath); + } + } else { + for (FilePickerListener listener : filePickerListeners) { + listener.onFilePickerCancelled(); + } + } + } else if (requestCode == REQUEST_SIMPLE_SIGN_IN || requestCode == REQUEST_WALLET_SYNC_SIGN_IN) { + if (resultCode == RESULT_OK) { + // user signed in + showSignedInUser(); + + if (requestCode == REQUEST_WALLET_SYNC_SIGN_IN) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + sp.edit().putBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_WALLET_SYNC_ENABLED, true).apply(); + + for (Fragment fragment : openNavFragments.values()) { + if (fragment instanceof WalletFragment) { + ((WalletFragment) fragment).onWalletSyncEnabled(); + } + } + scheduleWalletSyncTask(); + } + } + } else if (requestCode == REQUEST_VIDEO_CAPTURE || requestCode == REQUEST_TAKE_PHOTO) { + if (resultCode == RESULT_OK) { + PublishFragment publishFragment = null; + for (Fragment fragment : openNavFragments.values()) { + if (fragment instanceof PublishFragment) { + publishFragment = (PublishFragment) fragment; + break; + } + } + + Map params = new HashMap<>(); + params.put("directFilePath", cameraOutputFilename); + if (publishFragment != null) { + params.put("suggestedUrl", publishFragment.getSuggestedPublishUrl()); + } + openFragment(PublishFormFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH, params); + } + cameraOutputFilename = null; + } + } + + public void requestVideoCapture() { + Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); + if (intent.resolveActivity(getPackageManager()) != null) { + String outputPath = String.format("%s/record", Utils.getAppInternalStorageDir(this)); + File dir = new File(outputPath); + if (!dir.isDirectory()) { + dir.mkdirs(); + } + + cameraOutputFilename = String.format("%s/VID_%s.mp4", outputPath, Helper.FILESTAMP_FORMAT.format(new Date())); + Uri outputUri = FileProvider.getUriForFile(this, String.format("%s.fileprovider", getPackageName()), new File(cameraOutputFilename)); + intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri); + startActivityForResult(intent, REQUEST_VIDEO_CAPTURE); + return; + } + + showError(getString(R.string.cannot_capture_video)); + } + + public void requestTakePhoto() { + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + if (intent.resolveActivity(getPackageManager()) != null) { + String outputPath = String.format("%s/photos", Utils.getAppInternalStorageDir(this)); + File dir = new File(outputPath); + if (!dir.isDirectory()) { + dir.mkdirs(); + } + + cameraOutputFilename = String.format("%s/IMG_%s.jpg", outputPath, Helper.FILESTAMP_FORMAT.format(new Date())); + Uri outputUri = FileProvider.getUriForFile(this, String.format("%s.fileprovider", getPackageName()), new File(cameraOutputFilename)); + intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri); + startActivityForResult(intent, REQUEST_TAKE_PHOTO); + return; + } + + showError(getString(R.string.cannot_take_photo)); + } + + + + private void applyNavbarSigninPadding() { + int statusBarHeight = getStatusBarHeight(); + + View signInButton = findViewById(R.id.sign_in_button_container); + View signedInEmailContainer = findViewById(R.id.signed_in_email_container); + signInButton.setPadding(0, statusBarHeight, 0, 0); + signedInEmailContainer.setPadding(0, statusBarHeight, 0, 0); + } + + private void showSignedInUser() { + if (Lbryio.isSignedIn()) { + findViewById(R.id.sign_in_button_container).setVisibility(View.GONE); + findViewById(R.id.signed_in_email_container).setVisibility(View.VISIBLE); + ((TextView) findViewById(R.id.signed_in_email)).setText(Lbryio.getSignedInEmail()); + findViewById(R.id.sign_in_header_divider).setBackgroundColor(getResources().getColor(R.color.lightDivider)); + } + } + + private Fragment getCurrentFragment() { + int backCount = getSupportFragmentManager().getBackStackEntryCount(); + if (backCount > 0) { + try { + Fragment fragment = getSupportFragmentManager().getFragments().get(backCount - 1); + return fragment; + } catch (IndexOutOfBoundsException ex) { + return null; + } + } + return null; + } + + public void hideActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + } + public void showActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.show(); + } + } + private void renderStartupFailed(Map startupStages) { + Map startupStageIconIds = new HashMap<>(); + startupStageIconIds.put(STARTUP_STAGE_INSTALL_ID_LOADED, R.id.startup_stage_icon_install_id); + startupStageIconIds.put(STARTUP_STAGE_KNOWN_TAGS_LOADED, R.id.startup_stage_icon_known_tags); + startupStageIconIds.put(STARTUP_STAGE_EXCHANGE_RATE_LOADED, R.id.startup_stage_icon_exchange_rate); + startupStageIconIds.put(STARTUP_STAGE_USER_AUTHENTICATED, R.id.startup_stage_icon_user_authenticated); + startupStageIconIds.put(STARTUP_STAGE_NEW_INSTALL_DONE, R.id.startup_stage_icon_install_new); + startupStageIconIds.put(STARTUP_STAGE_SUBSCRIPTIONS_LOADED, R.id.startup_stage_icon_subscriptions_loaded); + startupStageIconIds.put(STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED, R.id.startup_stage_icon_subscriptions_resolved); + + for (Integer key : startupStages.keySet()) { + boolean stageDone = startupStages.get(key); + ImageView icon = findViewById(startupStageIconIds.get(key)); + icon.setImageResource(stageDone ? R.drawable.ic_check : R.drawable.ic_close); + icon.setColorFilter(stageDone ? Color.WHITE : Color.RED); + } + + findViewById(R.id.splash_view_loading_container).setVisibility(View.GONE); + findViewById(R.id.splash_view_error_container).setVisibility(View.VISIBLE); + } + + private void startup() { + final Context context = this; + Lbry.startupInit(); + + // perform some tasks before launching + (new AsyncTask() { + private Map startupStages = new HashMap<>(); + + private void initStartupStages() { + startupStages.put(STARTUP_STAGE_INSTALL_ID_LOADED, false); + startupStages.put(STARTUP_STAGE_KNOWN_TAGS_LOADED, false); + startupStages.put(STARTUP_STAGE_EXCHANGE_RATE_LOADED, false); + startupStages.put(STARTUP_STAGE_USER_AUTHENTICATED, false); + startupStages.put(STARTUP_STAGE_NEW_INSTALL_DONE, false); + startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_LOADED, false); + startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED, false); + } + protected void onPreExecute() { + hideActionBar(); + lockDrawer(); + findViewById(R.id.splash_view).setVisibility(View.VISIBLE); + LbryAnalytics.setCurrentScreen(MainActivity.this, "Splash", "Splash"); + initStartupStages(); + } + protected Boolean doInBackground(Void... params) { + BufferedReader reader = null; + try { + // Load the installation id from the file system + String lbrynetDir = String.format("%s/%s", Utils.getAppInternalStorageDir(context), "lbrynet"); + String installIdPath = String.format("%s/install_id", lbrynetDir); + reader = new BufferedReader(new InputStreamReader(new FileInputStream(installIdPath))); + String installId = reader.readLine(); + if (Helper.isNullOrEmpty(installId)) { + // no install_id found (first run didn't start the sdk successfully?) + startupStages.put(STARTUP_STAGE_INSTALL_ID_LOADED, false); + return false; + } + + Lbry.INSTALLATION_ID = installId; + startupStages.put(STARTUP_STAGE_INSTALL_ID_LOADED, true); + + SQLiteDatabase db = dbHelper.getReadableDatabase(); + List fetchedTags = DatabaseHelper.getTags(db); + Lbry.knownTags = Helper.mergeKnownTags(fetchedTags); + Collections.sort(Lbry.knownTags, new Tag()); + Lbry.followedTags = Helper.filterFollowedTags(Lbry.knownTags); + startupStages.put(STARTUP_STAGE_KNOWN_TAGS_LOADED, true); + + // load the exchange rate + Lbryio.loadExchangeRate(); + if (Lbryio.LBCUSDRate == 0) { + return false; + } + startupStages.put(STARTUP_STAGE_EXCHANGE_RATE_LOADED, true); + + Lbryio.authenticate(context); + if (Lbryio.currentUser == null) { + throw new Exception("Did not retrieve authenticated user."); + } + startupStages.put(STARTUP_STAGE_USER_AUTHENTICATED, true); + + Lbryio.newInstall(context); + startupStages.put(STARTUP_STAGE_NEW_INSTALL_DONE, true); + + // (light) fetch subscriptions + if (Lbryio.subscriptions.size() == 0) { + List subscriptions = new ArrayList<>(); + List subUrls = new ArrayList<>(); + JSONArray array = (JSONArray) Lbryio.parseResponse(Lbryio.call("subscription", "list", context)); + if (array != null) { + for (int i = 0; i < array.length(); i++) { + JSONObject item = array.getJSONObject(i); + String claimId = item.getString("claim_id"); + String channelName = item.getString("channel_name"); + + LbryUri url = new LbryUri(); + url.setChannelName(channelName); + url.setClaimId(claimId); + subscriptions.add(new Subscription(channelName, url.toString())); + subUrls.add(url.toString()); + } + Lbryio.subscriptions = subscriptions; + startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_LOADED, true); + + // resolve subscriptions + if (subUrls.size() > 0 && Lbryio.cacheResolvedSubscriptions.size() != Lbryio.subscriptions.size()) { + List resolvedSubs = Lbry.resolve(subUrls, Lbry.LBRY_TV_CONNECTION_STRING); + Lbryio.cacheResolvedSubscriptions = resolvedSubs; + } + // if no exceptions occurred here, subscriptions have been loaded and resolved + startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED, true); + } else { + // user has not subscribed to anything + startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_LOADED, true); + startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED, true); + } + } else { + startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_LOADED, true); + startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED, true); + } + } catch (Exception ex) { + // nope + android.util.Log.e(TAG, String.format("App startup failed: %s", ex.getMessage()), ex); + return false; + } finally { + Helper.closeCloseable(reader); + } + + return true; + } + protected void onPostExecute(Boolean startupSuccessful) { + if (!startupSuccessful) { + // show which startup stage failed + renderStartupFailed(startupStages); + appStarted = false; + return; + } + + findViewById(R.id.splash_view).setVisibility(View.GONE); + unlockDrawer(); + showActionBar(); + + if (navMenuAdapter != null) { + navMenuAdapter.setCurrentItem(NavMenuItem.ID_ITEM_FOLLOWING); + } + + loadLastFragment(); + showSignedInUser(); + fetchRewards(); + + checkUrlIntent(getIntent()); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_APP_LAUNCH); + appStarted = true; + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void fetchRewards() { + FetchRewardsTask task = new FetchRewardsTask(null, new FetchRewardsTask.FetchRewardsHandler() { + @Override + public void onSuccess(List rewards) { + Lbryio.updateRewardsLists(rewards); + for (Fragment fragment : openNavFragments.values()) { + if (fragment instanceof RewardsFragment) { + ((RewardsFragment) fragment).updateUnclaimedRewardsValue(); + } + } + + if (Lbryio.totalUnclaimedRewardAmount > 0) { + showFloatingUnclaimedRewards(); + updateRewardsUsdVale(); + } + } + + @Override + public void onError(Exception error) { + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void updateRewardsUsdVale() { + if (Lbryio.totalUnclaimedRewardAmount > 0) { + double usdRewardAmount = Lbryio.totalUnclaimedRewardAmount * Lbryio.LBCUSDRate; + if (navMenuAdapter != null) { + navMenuAdapter.setExtraLabelForItem( + NavMenuItem.ID_ITEM_REWARDS, + Lbryio.LBCUSDRate > 0 ? String.format("$%s", Helper.SIMPLE_CURRENCY_FORMAT.format(usdRewardAmount)) : null + ); + } + } + } + + public void showFloatingUnclaimedRewards() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + boolean notInterestedInRewards = sp.getBoolean(PREFERENCE_KEY_INTERNAL_REWARDS_NOT_INTERESTED, false); + if (notInterestedInRewards) { + return; + } + + ((TextView) findViewById(R.id.floating_reward_value)).setText(Helper.shortCurrencyFormat(Lbryio.totalUnclaimedRewardAmount)); + findViewById(R.id.floating_reward_container).setVisibility(View.VISIBLE); + } + + private void checkUrlIntent(Intent intent) { + if (intent != null) { + Uri data = intent.getData(); + if (data != null) { + String url = data.toString(); + // check special urls + if (url.startsWith("lbry://?")) { + String specialPath = url.substring(8); + if (specialRouteFragmentClassMap.containsKey(specialPath)) { + Class fragmentClass = specialRouteFragmentClassMap.get(specialPath); + if (fragmentClassNavIdMap.containsKey(fragmentClass)) { + Map params = new HashMap<>(); + String tag = intent.getStringExtra("tag"); + params.put("singleTag", tag); + + openFragment( + specialRouteFragmentClassMap.get(specialPath), + true, + fragmentClassNavIdMap.get(fragmentClass), + !Helper.isNullOrEmpty(tag) ? params : null + ); + } + } + + // unrecognised path will open the following by default + } else { + try { + LbryUri uri = LbryUri.parse(url); + if (uri.isChannel()) { + openChannelUrl(uri.toString()); + } else { + openFileUrl(uri.toString()); + } + } catch (LbryUriException ex) { + // pass + } + } + } + } + } + + private void loadLastFragment() { + Fragment fragment = getCurrentFragment(); + + if (fragment != null) { + openFragment(fragment, true); + } else { + openFragment(FollowingFragment.class, false, NavMenuItem.ID_ITEM_FOLLOWING); + } + } + + private void setSelectedNavMenuItemForFragment(Fragment fragment) { + if (fragment == null) { + // assume the first fragment is selected + navMenuAdapter.setCurrentItem(NavMenuItem.ID_ITEM_FOLLOWING); + return; + } + + Class fragmentClass = fragment.getClass(); + if (fragmentClassNavIdMap.containsKey(fragmentClass)) { + navMenuAdapter.setCurrentItem(fragmentClassNavIdMap.get(fragmentClass)); + } + } + + @Override + protected void onUserLeaveHint() { + if (startingShareActivity) { + // share activity triggered this, so reset the flag at this point new Handler().postDelayed(new Runnable() { @Override public void run() { - if (checkSdkReadyTask != null && checkSdkReadyTask.getStatus() != AsyncTask.Status.FINISHED) { - // task already running - return; - } - checkSdkReadyTask = new CheckSdkReadyTask(); - checkSdkReadyTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + startingShareActivity = false; } }, 1000); + return; } + if (startingPermissionRequest) { + return; + } + enterPIPMode(); + } + + protected boolean enterPIPMode() { + if (enteringPIPMode) { + return true; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && + appPlayer != null && + !startingFilePickerActivity && + !startingSignInFlowActivity) { + enteringPIPMode = true; + + getSupportActionBar().hide(); + findViewById(R.id.global_now_playing_card).setVisibility(View.GONE); + findViewById(R.id.pip_player).setVisibility(View.VISIBLE); + + PictureInPictureParams params = new PictureInPictureParams.Builder().build(); + enterPictureInPictureMode(params); + return true; + } + + return false; } private void checkNotificationOpenIntent(Intent intent) { @@ -200,71 +2402,15 @@ public class MainActivity extends FragmentActivity implements DefaultHardwareBac } private void logNotificationOpen(String name) { - if (firebaseAnalytics == null) { - firebaseAnalytics = FirebaseAnalytics.getInstance(this); - } - Bundle bundle = new Bundle(); bundle.putString("name", name); - firebaseAnalytics.logEvent("lbry_notification_open", bundle); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_LBRY_NOTIFICATION_OPEN, bundle); } - private void registerDownloadEventReceiver() { - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_EVENT); - downloadEventReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String downloadAction = intent.getStringExtra("action"); - String uri = intent.getStringExtra("uri"); - String outpoint = intent.getStringExtra("outpoint"); - String fileInfoJson = intent.getStringExtra("file_info"); - - - if (uri == null || outpoint == null || (fileInfoJson == null && !"abort".equals(downloadAction))) { - return; - } - - String eventName = null; - WritableMap params = Arguments.createMap(); - params.putString("uri", uri); - params.putString("outpoint", outpoint); - - ReactContext reactContext = mReactInstanceManager.getCurrentReactContext(); - if ("abort".equals(downloadAction)) { - eventName = "onDownloadAborted"; - if (reactContext != null) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params); - } - return; - } - - try { - JSONObject json = new JSONObject(fileInfoJson); - WritableMap fileInfo = JSONObjectToMap(json); - params.putMap("fileInfo", fileInfo); - - if (DownloadManager.ACTION_UPDATE.equals(downloadAction)) { - double progress = intent.getDoubleExtra("progress", 0); - params.putDouble("progress", progress); - eventName = "onDownloadUpdated"; - } else { - eventName = (DownloadManager.ACTION_START.equals(downloadAction)) ? "onDownloadStarted" : "onDownloadCompleted"; - } - - if (reactContext != null) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params); - } - } catch (JSONException ex) { - // pass - } - } - }; - registerReceiver(downloadEventReceiver, intentFilter); - } private void registerServiceActionsReceiver() { IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_EVENT); intentFilter.addAction(LbrynetService.LBRY_SDK_SERVICE_STARTED); intentFilter.addAction(LbrynetService.ACTION_STOP_SERVICE); serviceActionsReceiver = new BroadcastReceiver() { @@ -284,12 +2430,30 @@ public class MainActivity extends FragmentActivity implements DefaultHardwareBac notificationManager.notify(1, svcNotification); } }, 1000); + } else if (DownloadManager.ACTION_DOWNLOAD_EVENT.equalsIgnoreCase(action)) { + String downloadAction = intent.getStringExtra("action"); + String uri = intent.getStringExtra("uri"); + String outpoint = intent.getStringExtra("outpoint"); + String fileInfoJson = intent.getStringExtra("file_info"); + double progress = intent.getDoubleExtra("progress", 0); + if (uri == null || outpoint == null || (fileInfoJson == null && !"abort".equals(downloadAction))) { + return; + } + + for (DownloadActionListener listener : downloadActionListeners) { + listener.onDownloadAction(downloadAction, uri, outpoint, fileInfoJson, progress); + } } } }; registerReceiver(serviceActionsReceiver, intentFilter); } + private void unregisterReceivers() { + Helper.unregisterReceiver(requestsReceiver, this); + Helper.unregisterReceiver(serviceActionsReceiver, this); + } + private Notification buildServiceNotification() { NotificationCompat.Builder builder = new NotificationCompat.Builder(this, LbrynetService.NOTIFICATION_CHANNEL_ID); Intent contextIntent = new Intent(this, MainActivity.class); @@ -312,556 +2476,82 @@ public class MainActivity extends FragmentActivity implements DefaultHardwareBac return notification; } - private void registerNotificationsReceiver() { - // Background media receiver - IntentFilter filter = new IntentFilter(); - filter.addAction(BackgroundMediaModule.ACTION_PLAY); - filter.addAction(BackgroundMediaModule.ACTION_PAUSE); - notificationsReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - ReactContext reactContext = mReactInstanceManager.getCurrentReactContext(); - if (reactContext != null) { - if (BackgroundMediaModule.ACTION_PLAY.equals(action)) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("onBackgroundPlayPressed", null); - } - if (BackgroundMediaModule.ACTION_PAUSE.equals(action)) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("onBackgroundPausePressed", null); - } - } - } - }; - registerReceiver(notificationsReceiver, filter); + private static List buildNavMenu(Context context) { + NavMenuItem findContentGroup = new NavMenuItem(NavMenuItem.ID_GROUP_FIND_CONTENT, R.string.find_content, true, context); + NavMenuItem yourContentGroup = new NavMenuItem(NavMenuItem.ID_GROUP_YOUR_CONTENT, R.string.your_content, true, context); + NavMenuItem walletGroup = new NavMenuItem(NavMenuItem.ID_GROUP_WALLET, R.string.wallet, true, context); + NavMenuItem otherGroup = new NavMenuItem(NavMenuItem.ID_GROUP_OTHER, 0, true, context); + + findContentGroup.setItems(Arrays.asList( + new NavMenuItem(NavMenuItem.ID_ITEM_FOLLOWING, R.string.fa_heart, R.string.following, "Following", context), + new NavMenuItem(NavMenuItem.ID_ITEM_EDITORS_CHOICE, R.string.fa_star, R.string.editors_choice, "EditorsChoice", context), + new NavMenuItem(NavMenuItem.ID_ITEM_ALL_CONTENT, R.string.fa_globe_americas, R.string.all_content, "AllContent", context) + )); + + yourContentGroup.setItems(Arrays.asList( + new NavMenuItem(NavMenuItem.ID_ITEM_NEW_PUBLISH, R.string.fa_upload, R.string.new_publish, "NewPublish", context), + new NavMenuItem(NavMenuItem.ID_ITEM_CHANNELS, R.string.fa_at, R.string.channels, "Channels", context), + new NavMenuItem(NavMenuItem.ID_ITEM_LIBRARY, R.string.fa_download, R.string.library, "Library", context), + new NavMenuItem(NavMenuItem.ID_ITEM_PUBLISHES, R.string.fa_cloud_upload, R.string.publishes, "Publishes", context) + )); + + walletGroup.setItems(Arrays.asList( + new NavMenuItem(NavMenuItem.ID_ITEM_WALLET, R.string.fa_wallet, R.string.wallet, "Wallet", context), + new NavMenuItem(NavMenuItem.ID_ITEM_REWARDS, R.string.fa_award, R.string.rewards, "Rewards", context), + new NavMenuItem(NavMenuItem.ID_ITEM_INVITES, R.string.fa_user_friends, R.string.invites, "Invites", context) + )); + + otherGroup.setItems(Arrays.asList( + new NavMenuItem(NavMenuItem.ID_ITEM_SETTINGS, R.string.fa_cog, R.string.settings, "Settings", context), + new NavMenuItem(NavMenuItem.ID_ITEM_ABOUT, R.string.fa_mobile_alt, R.string.about, "About", context) + )); + + return Arrays.asList(findContentGroup, yourContentGroup, walletGroup, otherGroup); } - public void registerSmsReceiver() { - if (!hasPermission(Manifest.permission.RECEIVE_SMS, this)) { - // don't create the receiver if we don't have the read sms permission - return; - } - - IntentFilter smsFilter = new IntentFilter(); - smsFilter.addAction("android.provider.Telephony.SMS_RECEIVED"); - smsReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Get the message - Bundle bundle = intent.getExtras(); - if (bundle != null) { - Object[] pdus = (Object[]) bundle.get("pdus"); - if (pdus != null && pdus.length > 0) { - SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdus[0]); - String text = sms.getMessageBody(); - if (text == null || text.trim().length() == 0) { - return; - } - - // Retrieve verification code from the text message if it contains - // the strings "lbry", "verification code" and the colon (following the expected format) - text = text.toLowerCase(); - if (text.indexOf("lbry") > -1 && text.indexOf("verification code") > -1 && text.indexOf(":") > -1) { - String code = text.substring(text.lastIndexOf(":") + 1).trim(); - ReactContext reactContext = mReactInstanceManager.getCurrentReactContext(); - if (reactContext != null) { - WritableMap params = Arguments.createMap(); - params.putString("code", code); - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("onVerificationCodeReceived", params); - } - } - } - } - } - }; - registerReceiver(smsReceiver, smsFilter); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == OVERLAY_PERMISSION_REQ_CODE) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (!Settings.canDrawOverlays(this)) { - // SYSTEM_ALERT_WINDOW permission not granted... + // Flatten the structure into a single list for the RecyclerView + private static List flattenNavMenu(List navMenuItems) { + List flatMenu = new ArrayList<>(); + for (NavMenuItem item : navMenuItems) { + flatMenu.add(item); + if (item.getItems() != null) { + for (NavMenuItem subItem : item.getItems()) { + flatMenu.add(subItem); } } } - if (requestCode == DOCUMENT_PICKER_RESULT_CODE) { - ReactContext reactContext = mReactInstanceManager.getCurrentReactContext(); - if (reactContext != null) { - if (resultCode == RESULT_OK) { - Uri fileUri = data.getData(); - String filePath = getRealPathFromURI_API19(this, fileUri); - WritableMap params = Arguments.createMap(); - params.putString("path", filePath); - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("onDocumentPickerFilePicked", params); - } else if (resultCode == RESULT_CANCELED) { - // user canceled or request failed - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("onDocumentPickerCanceled", null); - } - } + return flatMenu; + } + + public void setNowPlayingClaim(Claim claim, String url) { + nowPlayingClaim = claim; + nowPlayingClaimUrl = url; + if (claim != null) { + ((TextView) findViewById(R.id.global_now_playing_title)).setText(nowPlayingClaim.getTitle()); + ((TextView) findViewById(R.id.global_now_playing_channel_title)).setText(nowPlayingClaim.getPublisherTitle()); } } - public static Activity getActivity() { - Activity activity = new Activity(); - activity = currentActivity; - return activity; - } - - @Override - public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { - ReactContext reactContext = mReactInstanceManager.getCurrentReactContext(); - switch (requestCode) { - case STORAGE_PERMISSION_REQ_CODE: - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= 23 && !Settings.canDrawOverlays(this)) { - Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + getPackageName())); - startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE); - } - if (reactContext != null) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("onStoragePermissionGranted", null); - } - } else { - // Permission not granted - /*Toast.makeText(this, - "LBRY requires access to your device storage to be able to download files and media." + - " Please enable the storage permission and restart the app.", Toast.LENGTH_LONG).show();*/ - if (reactContext != null) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("onStoragePermissionRefused", null); - } - } - break; - - case PHONE_STATE_PERMISSION_REQ_CODE: - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // Permission granted. Emit an onPhoneStatePermissionGranted event - if (reactContext != null) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("onPhoneStatePermissionGranted", null); - } - } else { - // Permission not granted. Simply show a message. - Toast.makeText(this, - "No permission granted to read your device state. Rewards cannot be claimed.", Toast.LENGTH_LONG).show(); - } - break; - - case RECEIVE_SMS_PERMISSION_REQ_CODE: - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // Permission granted. Emit an onPhoneStatePermissionGranted event - if (reactContext != null) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("onReceiveSmsPermissionGranted", null); - } - - // register the receiver - if (smsReceiver == null) { - registerSmsReceiver(); - } - } else { - // Permission not granted. Simply show a message. - Toast.makeText(this, - "No permission granted to receive your SMS messages. You may have to enter the verification code manually.", - Toast.LENGTH_LONG).show(); - } - break; - } - - if (permissionListener != null) { - permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults); + public void clearNowPlayingClaim() { + nowPlayingClaim = null; + nowPlayingClaimUrl = null; + findViewById(R.id.global_now_playing_card).setVisibility(View.GONE); + ((TextView) findViewById(R.id.global_now_playing_title)).setText(null); + ((TextView) findViewById(R.id.global_now_playing_channel_title)).setText(null); + if (appPlayer != null) { + appPlayer.setPlayWhenReady(false); } } - @Override - public void invokeDefaultOnBackPressed() { - super.onBackPressed(); - } + private static class CheckSdkReadyTask extends AsyncTask { + private Context context; + private List listeners; - @Override - protected void onPause() { - super.onPause(); - - if (mReactInstanceManager != null) { - mReactInstanceManager.onHostPause(this); + public CheckSdkReadyTask(Context context, List listeners) { + this.context = context; + this.listeners = new ArrayList<>(listeners); } - } - - @Override - protected void onResume() { - super.onResume(); - - SharedPreferences sp = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - LbrynetService.setDHTEnabled(sp.getBoolean(UtilityModule.DHT_ENABLED, false)); - - serviceRunning = isServiceRunning(this, LbrynetService.class); - if (!serviceRunning) { - ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice"); - } - checkSdkReady(); - - if (mReactInstanceManager != null) { - mReactInstanceManager.onHostResume(this, this); - } - } - - @Override - protected void onDestroy() { - // check service running setting and end it here - SharedPreferences sp = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - boolean shouldKeepDaemonRunning = sp.getBoolean(SETTING_KEEP_DAEMON_RUNNING, true); - if (!shouldKeepDaemonRunning) { - serviceRunning = isServiceRunning(this, LbrynetService.class); - if (serviceRunning) { - ServiceHelper.stop(this, LbrynetService.class); - } - } - - if (notificationsReceiver != null) { - unregisterReceiver(notificationsReceiver); - notificationsReceiver = null; - } - - if (smsReceiver != null) { - unregisterReceiver(smsReceiver); - smsReceiver = null; - } - - if (downloadEventReceiver != null) { - unregisterReceiver(downloadEventReceiver); - downloadEventReceiver = null; - } - - if (serviceActionsReceiver != null) { - unregisterReceiver(serviceActionsReceiver); - serviceActionsReceiver = null; - } - - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); - notificationManager.cancel(BackgroundMediaModule.NOTIFICATION_ID); - notificationManager.cancel(DownloadManager.DOWNLOAD_NOTIFICATION_GROUP_ID); - if (downloadNotificationIds != null) { - for (int i = 0; i < downloadNotificationIds.size(); i++) { - notificationManager.cancel(downloadNotificationIds.get(i)); - } - } - if (receivedStopService || !isServiceRunning(this, LbrynetService.class)) { - notificationManager.cancelAll(); - } - super.onDestroy(); - - if (mReactInstanceManager != null) { - mReactInstanceManager.onHostDestroy(this); - } - } - - @Override - public void onBackPressed() { - if (mReactInstanceManager != null) { - mReactInstanceManager.onBackPressed(); - } else { - super.onBackPressed(); - } - } - - @TargetApi(Build.VERSION_CODES.M) - public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) { - permissionListener = listener; - ActivityCompat.requestPermissions(this, permissions, requestCode); - } - - @Override - public void onNewIntent(Intent intent) { - if (mReactInstanceManager != null) { - mReactInstanceManager.onNewIntent(intent); - } - - if (intent != null) { - int sourceNotificationId = intent.getIntExtra(SOURCE_NOTIFICATION_ID_KEY, -1); - if (sourceNotificationId > -1) { - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); - notificationManager.cancel(sourceNotificationId); - } - - checkNotificationOpenIntent(intent); - } - - super.onNewIntent(intent); - } - - private static void checkPermission(String permission, int requestCode, String rationale, Context context, boolean forceRequest) { - if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { - // Should we show an explanation? - if (!forceRequest && ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, permission)) { - Toast.makeText(context, rationale, Toast.LENGTH_LONG).show(); - } else { - ActivityCompat.requestPermissions((Activity) context, new String[] { permission }, requestCode); - } - } - } - - private static void checkPermission(String permission, int requestCode, String rationale, Context context) { - checkPermission(permission, requestCode, rationale, context, false); - } - - public static boolean hasPermission(String permission, Context context) { - return (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED); - } - - public static void checkPhoneStatePermission(Context context) { - // Request read phone state permission - checkPermission(Manifest.permission.READ_PHONE_STATE, - PHONE_STATE_PERMISSION_REQ_CODE, - "LBRY requires optional access to be able to identify your device for rewards. " + - "You cannot claim rewards without this permission.", - context, - true); - } - - public static void checkReceiveSmsPermission(Context context) { - // Request read phone state permission - checkPermission(Manifest.permission.RECEIVE_SMS, - RECEIVE_SMS_PERMISSION_REQ_CODE, - "LBRY requires access to be able to read a verification text message for rewards.", - context, - true); - } - - public static void checkStoragePermission(Context context) { - // Request read phone state permission - checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, - STORAGE_PERMISSION_REQ_CODE, - "LBRY requires access to your device storage to be able to download files and media.", - context, - true); - } - - public static boolean isServiceRunning(Context context, Class serviceClass) { - ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) { - if (serviceClass.getName().equals(serviceInfo.service.getClassName())) { - return true; - } - } - - return false; - } - - private static WritableMap JSONObjectToMap(JSONObject jsonObject) throws JSONException { - WritableMap map = Arguments.createMap(); - Iterator keys = jsonObject.keys(); - while(keys.hasNext()) { - String key = keys.next(); - Object value = jsonObject.get(key); - if (value instanceof JSONArray) { - map.putArray(key, JSONArrayToList((JSONArray) value)); - } else if (value instanceof JSONObject) { - map.putMap(key, JSONObjectToMap((JSONObject) value)); - } else if (value instanceof Boolean) { - map.putBoolean(key, (Boolean) value); - } else if (value instanceof Integer) { - map.putInt(key, (Integer) value); - } else if (value instanceof Double) { - map.putDouble(key, (Double) value); - } else if (value instanceof String) { - map.putString(key, (String) value); - } else { - map.putString(key, value.toString()); - } - } - - return map; - } - - private static WritableArray JSONArrayToList(JSONArray jsonArray) throws JSONException { - WritableArray array = Arguments.createArray(); - for(int i = 0; i < jsonArray.length(); i++) { - Object value = jsonArray.get(i); - if (value instanceof JSONArray) { - array.pushArray(JSONArrayToList((JSONArray) value)); - } else if (value instanceof JSONObject) { - array.pushMap(JSONObjectToMap((JSONObject) value)); - } else if (value instanceof Boolean) { - array.pushBoolean((Boolean) value); - } else if (value instanceof Integer) { - array.pushInt((Integer) value); - } else if (value instanceof Double) { - array.pushDouble((Double) value); - } else if (value instanceof String) { - array.pushString((String) value); - } else { - array.pushString(value.toString()); - } - } - - return array; - } - - /** - * https://gist.github.com/HBiSoft/15899990b8cd0723c3a894c1636550a8 - */ - @SuppressLint("NewApi") - public static String getRealPathFromURI_API19(final Context context, final Uri uri) { - final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; - - // DocumentProvider - if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { - // ExternalStorageProvider - if (isExternalStorageDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - // This is for checking Main Memory - if ("primary".equalsIgnoreCase(type)) { - if (split.length > 1) { - return Environment.getExternalStorageDirectory() + "/" + split[1]; - } else { - return Environment.getExternalStorageDirectory() + "/"; - } - // This is for checking SD Card - } else { - return "storage" + "/" + docId.replace(":", "/"); - } - - } - // DownloadsProvider - else if (isDownloadsDocument(uri)) { - String fileName = getFilePath(context, uri); - if (fileName != null) { - return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName; - } - - String id = DocumentsContract.getDocumentId(uri); - if (id.startsWith("raw:")) { - id = id.replaceFirst("raw:", ""); - File file = new File(id); - if (file.exists()) - return id; - } - - final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - return getDataColumn(context, contentUri, null, null); - } - // MediaProvider - else if (isMediaDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - Uri contentUri = null; - if ("image".equals(type)) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else if ("video".equals(type)) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } else if ("audio".equals(type)) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - } - - final String selection = "_id=?"; - final String[] selectionArgs = new String[]{ - split[1] - }; - - return getDataColumn(context, contentUri, selection, selectionArgs); - } - } - // MediaStore (and general) - else if ("content".equalsIgnoreCase(uri.getScheme())) { - - // Return the remote address - if (isGooglePhotosUri(uri)) - return uri.getLastPathSegment(); - - return getDataColumn(context, uri, null, null); - } - // File - else if ("file".equalsIgnoreCase(uri.getScheme())) { - return uri.getPath(); - } - - return null; - } - - public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { - Cursor cursor = null; - final String column = "_data"; - final String[] projection = { - column - }; - - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); - if (cursor != null && cursor.moveToFirst()) { - final int index = cursor.getColumnIndexOrThrow(column); - return cursor.getString(index); - } - } finally { - if (cursor != null) - cursor.close(); - } - return null; - } - - - public static String getFilePath(Context context, Uri uri) { - Cursor cursor = null; - final String[] projection = { MediaStore.MediaColumns.DISPLAY_NAME }; - - try { - cursor = context.getContentResolver().query(uri, projection, null, null, null); - if (cursor != null && cursor.moveToFirst()) { - final int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME); - return cursor.getString(index); - } - } finally { - if (cursor != null) - cursor.close(); - } - return null; - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is ExternalStorageProvider. - */ - public static boolean isExternalStorageDocument(Uri uri) { - return "com.android.externalstorage.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is DownloadsProvider. - */ - public static boolean isDownloadsDocument(Uri uri) { - return "com.android.providers.downloads.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is MediaProvider. - */ - public static boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is Google Photos. - */ - public static boolean isGooglePhotosUri(Uri uri) { - return "com.google.android.apps.photos.content".equals(uri.getAuthority()); - } - - private class CheckSdkReadyTask extends AsyncTask { public Boolean doInBackground(Void... params) { boolean sdkReady = false; @@ -870,20 +2560,13 @@ public class MainActivity extends FragmentActivity implements DefaultHardwareBac if (response != null) { JSONObject result = new JSONObject(response); JSONObject status = result.getJSONObject("result"); - - // send status response for splash page updates - WritableMap sdkStatus = JSONObjectToMap(status); - ReactContext reactContext = mReactInstanceManager.getCurrentReactContext(); - if (reactContext != null) { - WritableMap evtParams = Arguments.createMap(); - evtParams.putMap("status", sdkStatus); - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onSdkStatusResponse", evtParams); + if (!Lbry.IS_STATUS_PARSED) { + Lbry.parseStatus(status.toString()); } + // TODO: Broadcast startup status changes JSONObject startupStatus = status.getJSONObject("startup_status"); - sdkReady = startupStatus.has("stream_manager") && startupStatus.has("wallet") && - startupStatus.getBoolean("stream_manager") && startupStatus.getBoolean("wallet") && - (status.getJSONObject("wallet").getLong("blocks_behind") <= 0); + sdkReady = startupStatus.getBoolean("file_manager") && startupStatus.getBoolean("wallet"); } } catch (ConnectException ex) { // pass @@ -894,37 +2577,224 @@ public class MainActivity extends FragmentActivity implements DefaultHardwareBac return sdkReady; } protected void onPostExecute(Boolean sdkReady) { - lbrySdkReady = sdkReady; - ReactContext reactContext = mReactInstanceManager.getCurrentReactContext(); - if (sdkReady && reactContext != null) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onSdkReady", null); + Lbry.SDK_READY = sdkReady; + if (context != null) { + if (sdkReady) { + context.sendBroadcast(new Intent(ACTION_SDK_READY)); + + // update listeners + for (SdkStatusListener listener : listeners) { + if (listener != null) { + listener.onSdkReady(); + } + } + } else if (context instanceof MainActivity) { + ((MainActivity) context).checkSdkReady(); + } + } + } + } + + public void showNavigationBackIcon() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + if (toggle != null) { + TypedArray a = getTheme().obtainStyledAttributes(R.style.AppTheme, new int[] {R.attr.homeAsUpIndicator}); + int attributeResourceId = a.getResourceId(0, 0); + Drawable drawable = ResourcesCompat.getDrawable(getResources(), attributeResourceId, null); + DrawableCompat.setTint(drawable, ContextCompat.getColor(this, R.color.actionBarForeground)); + + toggle.setDrawerIndicatorEnabled(false); + toggle.setHomeAsUpIndicator(drawable); + } + } + + private void closeDrawer() { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + drawer.closeDrawer(GravityCompat.START); + } + + public void lockDrawer() { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + } + + public void unlockDrawer() { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); + } + + public void restoreToggle() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(false); + actionBar.setTitle(null); + } + if (toggle != null) { + toggle.setDrawerIndicatorEnabled(true); + } + unlockDrawer(); + showSearchBar(); + } + + public void hideSearchBar() { + findViewById(R.id.wunderbar_container).setVisibility(View.GONE); + } + + public void showSearchBar() { + findViewById(R.id.wunderbar_container).setVisibility(View.VISIBLE); + clearWunderbarFocus(findViewById(R.id.wunderbar)); + } + + @Override + public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { + inPictureInPictureMode = isInPictureInPictureMode; + enteringPIPMode = false; + if (isInPictureInPictureMode) { + // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode. + renderPictureInPictureMode(); + } else { + // Restore the full-screen UI. + renderFullMode(); + } + } + + protected void onStop() { + if (appPlayer != null && inPictureInPictureMode) { + appPlayer.setPlayWhenReady(false); + } + super.onStop(); + } + + public void openFragment(Fragment fragment, boolean allowNavigateBack) { + Fragment currentFragment = getCurrentFragment(); + if (currentFragment != null && currentFragment.equals(fragment)) { + return; + } + + try { + FragmentManager manager = getSupportFragmentManager(); + FragmentTransaction transaction = manager.beginTransaction().replace(R.id.content_main, fragment); + if (allowNavigateBack) { + transaction.addToBackStack(null); + } + transaction.commit(); + } catch (Exception ex) { + // pass + } + } + + public void openFragment(Class fragmentClass, boolean allowNavigateBack, int navItemId) { + openFragment(fragmentClass, allowNavigateBack, navItemId, null); + } + + private static String buildNavFragmentKey(Class fragmentClass, int navItemId, Map params) { + if (params != null && params.containsKey("url")) { + return String.format("%s-%d-%s", fragmentClass.getName(), navItemId, params.get("url").toString()); + } + + return String.format("%s-%d", fragmentClass.getName(), navItemId); + } + + public void openFragment(Class fragmentClass, boolean allowNavigateBack, int navItemId, Map params) { + try { + String key = buildNavFragmentKey(fragmentClass, navItemId, params); + Fragment fragment = openNavFragments.containsKey(key) ? openNavFragments.get(key) : (Fragment) fragmentClass.newInstance(); + if (fragment instanceof BaseFragment) { + ((BaseFragment) fragment).setParams(params); + } + Fragment currentFragment = getCurrentFragment(); + if (currentFragment != null && currentFragment.equals(fragment)) { + return; } - if (!sdkReady) { - checkSdkReady(); + //fragment.setRetainInstance(true); + FragmentManager manager = getSupportFragmentManager(); + FragmentTransaction transaction = manager.beginTransaction().replace(R.id.content_main, fragment); + if (allowNavigateBack) { + transaction.addToBackStack(null); + } + transaction.commit(); + + if (navItemId > -1) { + openNavFragments.put(key, fragment); + } + } catch (Exception ex) { + // pass + } + } + + public void fetchOwnChannels() { + ClaimListTask task = new ClaimListTask(Claim.TYPE_CHANNEL, null, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + Lbry.ownChannels = Helper.filterDeletedClaims(new ArrayList<>(claims)); + for (FetchChannelsListener listener : fetchChannelsListeners) { + listener.onChannelsFetched(claims); + } + } + + @Override + public void onError(Exception error) { + // pass + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void fetchOwnClaims() { + ClaimListTask task = new ClaimListTask(Arrays.asList(Claim.TYPE_STREAM, Claim.TYPE_REPOST), null, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + Lbry.ownClaims = Helper.filterDeletedClaims(new ArrayList<>(claims)); + for (FetchClaimsListener listener : fetchClaimsListeners) { + listener.onClaimsFetched(claims); + } + } + + @Override + public void onError(Exception error) { } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void checkSyncedWallet() { + String password = Utils.getSecureValue(SECURE_VALUE_KEY_SAVED_PASSWORD, this, Lbry.KEYSTORE); + // Just check if the current user has a synced wallet, no need to do anything else here + SyncGetTask task = new SyncGetTask(password, false, null, new DefaultSyncTaskHandler() { + @Override + public void onSyncGetSuccess(WalletSync walletSync) { + Lbryio.userHasSyncedWallet = true; + Lbryio.setLastWalletSync(walletSync); + Lbryio.setLastRemoteHash(walletSync.getHash()); + } + + @Override + public void onSyncGetWalletNotFound() { } + @Override + public void onSyncGetError(Exception error) { } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void requestPermission(String permission, int requestCode, String rationale, Context context, boolean forceRequest) { + if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { + if (!forceRequest && ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, permission)) { + Toast.makeText(context, rationale, Toast.LENGTH_LONG).show(); + } else { + startingPermissionRequest = true; + ActivityCompat.requestPermissions((Activity) context, new String[] { permission }, requestCode); } } } - - public static class LaunchTiming { - private Date start; - private boolean coldStart; - - public LaunchTiming(Date start) { - this.start = start; - } - - public Date getStart() { - return start; - } - public void setStart(Date start) { - this.start = start; - } - public boolean isColdStart() { - return coldStart; - } - public void setColdStart(boolean coldStart) { - this.coldStart = coldStart; - } + + public static boolean hasPermission(String permission, Context context) { + return (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED); + } + + public interface BackPressInterceptor { + boolean onBackPressed(); } } diff --git a/app/src/main/java/io/lbry/browser/VerificationActivity.java b/app/src/main/java/io/lbry/browser/VerificationActivity.java new file mode 100644 index 00000000..cf819cb6 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/VerificationActivity.java @@ -0,0 +1,272 @@ +package io.lbry.browser; + +import android.content.Intent; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.View; + +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.widget.ViewPager2; + +import com.google.android.material.snackbar.Snackbar; + +import java.util.Arrays; + +import io.lbry.browser.adapter.VerificationPagerAdapter; +import io.lbry.browser.listener.SignInListener; +import io.lbry.browser.listener.WalletSyncListener; +import io.lbry.browser.model.lbryinc.User; +import io.lbry.browser.tasks.lbryinc.FetchCurrentUserTask; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.Lbryio; + +public class VerificationActivity extends FragmentActivity implements SignInListener, WalletSyncListener { + + public static final int VERIFICATION_FLOW_SIGN_IN = 1; + public static final int VERIFICATION_FLOW_REWARDS = 2; + public static final int VERIFICATION_FLOW_WALLET = 3; + + private String email; + private boolean signedIn; + private int flow; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + signedIn = Lbryio.isSignedIn(); + Intent intent = getIntent(); + if (intent != null) { + flow = intent.getIntExtra("flow", -1); + if (flow == -1 || (flow == VERIFICATION_FLOW_SIGN_IN && signedIn)) { + // no flow specified (or user is already signed in), just exit + setResult(signedIn ? RESULT_OK : RESULT_CANCELED); + finish(); + return; + } + } + + if (!Arrays.asList(VERIFICATION_FLOW_SIGN_IN, VERIFICATION_FLOW_REWARDS, VERIFICATION_FLOW_WALLET).contains(flow)) { + // invalid flow specified + setResult(RESULT_CANCELED); + finish(); + return; + } + + setContentView(R.layout.activity_verification); + ViewPager2 viewPager = findViewById(R.id.verification_pager); + viewPager.setUserInputEnabled(false); + viewPager.setSaveEnabled(false); + viewPager.setAdapter(new VerificationPagerAdapter(this)); + + findViewById(R.id.verification_close_button).setVisibility(View.VISIBLE); + findViewById(R.id.verification_close_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + setResult(RESULT_CANCELED); + finish(); + } + }); + } + + public void onResume() { + super.onResume(); + LbryAnalytics.setCurrentScreen(this, "Verification", "Verification"); + checkFlow(); + } + + public void checkFlow() { + ViewPager2 viewPager = findViewById(R.id.verification_pager); + if (Lbryio.isSignedIn()) { + boolean flowHandled = false; + if (flow == VERIFICATION_FLOW_WALLET) { + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_WALLET, false); + flowHandled = true; + } else if (flow == VERIFICATION_FLOW_REWARDS) { + User user = Lbryio.currentUser; + if (!user.isIdentityVerified()) { + // phone number verification required + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_PHONE, false); + flowHandled = true; + } else if (!user.isRewardApproved()) { + // manual verification required + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_MANUAL, false); + flowHandled = true; + } + } + + if (!flowHandled) { + // user has already been verified and or reward approved + setResult(RESULT_CANCELED); + finish(); + return; + } + } + } + + public void showLoading() { + findViewById(R.id.verification_loading_progress).setVisibility(View.VISIBLE); + findViewById(R.id.verification_pager).setVisibility(View.INVISIBLE); + findViewById(R.id.verification_close_button).setVisibility(View.GONE); + } + + public void hideLoading() { + findViewById(R.id.verification_loading_progress).setVisibility(View.GONE); + findViewById(R.id.verification_pager).setVisibility(View.VISIBLE); + } + + @Override + public void onBackPressed() { + // ignore back press + return; + } + + public void onEmailAdded(String email) { + this.email = email; + findViewById(R.id.verification_close_button).setVisibility(View.GONE); + + Bundle bundle = new Bundle(); + bundle.putString("email", email); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_EMAIL_ADDED, bundle); + } + public void onEmailEdit() { + findViewById(R.id.verification_close_button).setVisibility(View.VISIBLE); + } + public void onEmailVerified() { + Snackbar.make(findViewById(R.id.verification_pager), R.string.sign_in_successful, Snackbar.LENGTH_LONG).show(); + sendBroadcast(new Intent(MainActivity.ACTION_USER_SIGN_IN_SUCCESS)); + + Bundle bundle = new Bundle(); + bundle.putString("email", email); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_EMAIL_VERIFIED, bundle); + + if (flow == VERIFICATION_FLOW_SIGN_IN) { + final Intent resultIntent = new Intent(); + resultIntent.putExtra("flow", VERIFICATION_FLOW_SIGN_IN); + resultIntent.putExtra("email", email); + + // only sign in required, don't do anything else + showLoading(); + FetchCurrentUserTask task = new FetchCurrentUserTask(new FetchCurrentUserTask.FetchUserTaskHandler() { + @Override + public void onSuccess(User user) { + Lbryio.currentUser = user; + setResult(RESULT_OK, resultIntent); + finish(); + } + + @Override + public void onError(Exception error) { + showFetchUserError(error.getMessage()); + hideLoading(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + // change pager view depending on flow + showLoading(); + FetchCurrentUserTask task = new FetchCurrentUserTask(new FetchCurrentUserTask.FetchUserTaskHandler() { + @Override + public void onSuccess(User user) { + hideLoading(); + findViewById(R.id.verification_close_button).setVisibility(View.VISIBLE); + + Lbryio.currentUser = user; + ViewPager2 viewPager = findViewById(R.id.verification_pager); + // for rewards, (show phone verification if not done, or manual verification if required) + if (flow == VERIFICATION_FLOW_REWARDS) { + if (!user.isIdentityVerified()) { + // phone number verification required + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_PHONE, false); + } else if (!user.isRewardApproved()) { + // manual verification required + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_MANUAL, false); + } else { + // fully verified + setResult(RESULT_OK); + finish(); + } + } else if (flow == VERIFICATION_FLOW_WALLET) { + // for wallet sync, if password unlock is required, show password entry page + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_WALLET, false); + } + } + @Override + public void onError(Exception error) { + showFetchUserError(error.getMessage()); + hideLoading(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + @Override + public void onPhoneAdded(String countryCode, String phoneNumber) { + + } + + @Override + public void onPhoneVerified() { + showLoading(); + FetchCurrentUserTask task = new FetchCurrentUserTask(new FetchCurrentUserTask.FetchUserTaskHandler() { + @Override + public void onSuccess(User user) { + Lbryio.currentUser = user; + if (user.isIdentityVerified() && user.isRewardApproved()) { + // verified for rewards + LbryAnalytics.logEvent(LbryAnalytics.EVENT_REWARD_ELIGIBILITY_COMPLETED); + + setResult(RESULT_OK); + finish(); + return; + } + + // show manual verification page if the user is still not reward approved + ViewPager2 viewPager = findViewById(R.id.verification_pager); + viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_MANUAL, false); + hideLoading(); + } + + @Override + public void onError(Exception error) { + showFetchUserError(error.getMessage()); + hideLoading(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void showFetchUserError(String message) { + Snackbar.make(findViewById(R.id.verification_pager), message, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } + + @Override + public void onManualVerifyContinue() { + setResult(RESULT_OK); + finish(); + } + + @Override + public void onWalletSyncProcessing() { + findViewById(R.id.verification_close_button).setVisibility(View.GONE); + } + @Override + public void onWalletSyncWaitingForInput() { + findViewById(R.id.verification_close_button).setVisibility(View.VISIBLE); + } + + @Override + public void onWalletSyncEnabled() { + findViewById(R.id.verification_close_button).setVisibility(View.VISIBLE); + setResult(RESULT_OK); + finish(); + } + + @Override + public void onWalletSyncFailed(Exception error) { + findViewById(R.id.verification_close_button).setVisibility(View.VISIBLE); + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/ChannelFilterListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/ChannelFilterListAdapter.java new file mode 100644 index 00000000..a4b0873d --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/ChannelFilterListAdapter.java @@ -0,0 +1,125 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.Claim; +import io.lbry.browser.listener.ChannelItemSelectionListener; +import io.lbry.browser.utils.Helper; +import lombok.Getter; +import lombok.Setter; + +public class ChannelFilterListAdapter extends RecyclerView.Adapter { + private Context context; + private List items; + @Getter + @Setter + private Claim selectedItem; + @Setter + private ChannelItemSelectionListener listener; + + public ChannelFilterListAdapter(Context context) { + this.context = context; + this.items = new ArrayList<>(); + + // Always list the placeholder as the first item + Claim claim = new Claim(); + claim.setPlaceholder(true); + items.add(claim); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected View mediaContainer; + protected View alphaContainer; + protected View allView; + protected ImageView thumbnailView; + protected TextView alphaView; + protected TextView titleView; + public ViewHolder(View v) { + super(v); + mediaContainer = v.findViewById(R.id.channel_filter_media_container); + alphaContainer = v.findViewById(R.id.channel_filter_no_thumbnail); + alphaView = v.findViewById(R.id.channel_filter_alpha_view); + thumbnailView = v.findViewById(R.id.channel_filter_thumbnail); + titleView = v.findViewById(R.id.channel_filter_title); + allView = v.findViewById(R.id.channel_filter_all_container); + } + } + + public int getItemCount() { + return items != null ? items.size() : 0; + } + + public boolean isClaimSelected(Claim claim) { + return claim.equals(selectedItem); + } + + public void clearClaims() { + items = new ArrayList<>(items.subList(0, 1)); + notifyDataSetChanged(); + } + + public void addClaims(List claims) { + for (Claim claim : claims) { + if (!items.contains(claim)) { + items.add(claim); + } + } + notifyDataSetChanged(); + } + + @Override + public ChannelFilterListAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_channel_filter, root, false); + return new ChannelFilterListAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(ChannelFilterListAdapter.ViewHolder vh, int position) { + Claim claim = items.get(position); + vh.alphaView.setVisibility(claim.isPlaceholder() ? View.GONE : View.VISIBLE); + vh.titleView.setVisibility(claim.isPlaceholder() ? View.INVISIBLE : View.VISIBLE); + vh.allView.setVisibility(claim.isPlaceholder() ? View.VISIBLE : View.GONE); + + vh.titleView.setText(Helper.isNullOrEmpty(claim.getTitle()) ? claim.getName() : claim.getTitle()); + String thumbnailUrl = claim.getThumbnailUrl(); + if (!Helper.isNullOrEmpty(thumbnailUrl) && context != null) { + Glide.with(context.getApplicationContext()).load(thumbnailUrl).apply(RequestOptions.circleCropTransform()).into(vh.thumbnailView); + } + vh.alphaContainer.setVisibility(claim.isPlaceholder() || Helper.isNullOrEmpty(thumbnailUrl) ? View.VISIBLE : View.GONE); + vh.thumbnailView.setVisibility(claim.isPlaceholder() || Helper.isNullOrEmpty(thumbnailUrl) ? View.GONE : View.VISIBLE); + vh.alphaView.setText(claim.isPlaceholder() ? null : claim.getName() != null ? claim.getName().substring(1, 2) : ""); + + int bgColor = Helper.generateRandomColorForValue(claim.getClaimId()); + Helper.setIconViewBackgroundColor(vh.alphaContainer, bgColor, claim.isPlaceholder(), context); + + vh.itemView.setSelected(isClaimSelected(claim)); + vh.itemView.setOnClickListener(view -> { + if (claim.isPlaceholder()) { + selectedItem = null; + if (listener != null) { + listener.onChannelSelectionCleared(); + } + } else if (!claim.equals(selectedItem)) { + selectedItem = claim; + if (listener != null) { + listener.onChannelItemSelected(claim); + } + } + notifyDataSetChanged(); + }); + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java new file mode 100644 index 00000000..ce8bbce3 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java @@ -0,0 +1,496 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.snackbar.Snackbar; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.listener.SelectionModeListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; +import lombok.Getter; +import lombok.Setter; + +public class ClaimListAdapter extends RecyclerView.Adapter { + private static final int VIEW_TYPE_STREAM = 1; + private static final int VIEW_TYPE_CHANNEL = 2; + private static final int VIEW_TYPE_FEATURED = 3; // featured search result + + private Map quickClaimIdMap; + private Map quickClaimUrlMap; + private Map notFoundClaimIdMap; + private Map notFoundClaimUrlMap; + + @Setter + private boolean hideFee; + @Setter + private boolean canEnterSelectionMode; + private Context context; + private List items; + private List selectedItems; + @Setter + private ClaimListItemListener listener; + @Getter + @Setter + private boolean inSelectionMode; + @Setter + private SelectionModeListener selectionModeListener; + private float scale; + + public ClaimListAdapter(List items, Context context) { + this.context = context; + this.items = new ArrayList<>(items); + this.selectedItems = new ArrayList<>(); + quickClaimIdMap = new HashMap<>(); + quickClaimUrlMap = new HashMap<>(); + notFoundClaimIdMap = new HashMap<>(); + notFoundClaimUrlMap = new HashMap<>(); + if (context != null) { + scale = context.getResources().getDisplayMetrics().density; + } + } + + public List getSelectedItems() { + return this.selectedItems; + } + public int getSelectedCount() { + return selectedItems != null ? selectedItems.size() : 0; + } + public void clearSelectedItems() { + this.selectedItems.clear(); + } + public boolean isClaimSelected(Claim claim) { + return selectedItems.contains(claim); + } + + public Claim getFeaturedItem() { + for (Claim claim : items) { + if (claim.isFeatured()) { + return claim; + } + } + return null; + } + + public void removeFeaturedItem() { + int featuredIndex = -1; + for (int i = 0; i < items.size(); i++) { + if (items.get(i).isFeatured()) { + featuredIndex = i; + break; + } + } + if (featuredIndex > -1) { + items.remove(featuredIndex); + } + } + + public List getItems() { + return new ArrayList<>(this.items); + } + + public void updateSigningChannelForClaim(Claim resolvedClaim) { + for (Claim claim : items) { + if (claim.getClaimId().equalsIgnoreCase(resolvedClaim.getClaimId())) { + claim.setSigningChannel(resolvedClaim.getSigningChannel()); + } + } + } + + public void clearItems() { + clearSelectedItems(); + this.items.clear(); + quickClaimIdMap.clear(); + quickClaimUrlMap.clear(); + notFoundClaimIdMap.clear(); + notFoundClaimUrlMap.clear(); + notifyDataSetChanged(); + } + + public Claim getLastItem() { + return items.size() > 0 ? items.get(items.size() - 1) : null; + } + + public void addFeaturedItem(Claim claim) { + items.add(0, claim); + notifyDataSetChanged(); + } + + public void addItems(List claims) { + for (Claim claim : claims) { + if (!items.contains(claim)) { + items.add(claim); + } + } + + notFoundClaimUrlMap.clear(); + notFoundClaimIdMap.clear(); + notifyDataSetChanged(); + } + public void setItems(List claims) { + items = new ArrayList<>(claims); + notifyDataSetChanged(); + } + + public void removeItems(List claims) { + items.removeAll(claims); + notifyDataSetChanged(); + } + + public void removeItem(Claim claim) { + items.remove(claim); + selectedItems.remove(claim); + notifyDataSetChanged(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected View feeContainer; + protected TextView feeView; + protected ImageView thumbnailView; + protected View noThumbnailView; + protected TextView alphaView; + protected TextView vanityUrlView; + protected TextView durationView; + protected TextView titleView; + protected TextView publisherView; + protected TextView publishTimeView; + protected TextView pendingTextView; + protected View repostInfoView; + protected TextView repostChannelView; + protected View selectedOverlayView; + protected TextView fileSizeView; + protected ProgressBar downloadProgressView; + protected TextView deviceView; + public ViewHolder(View v) { + super(v); + feeContainer = v.findViewById(R.id.claim_fee_container); + feeView = v.findViewById(R.id.claim_fee); + alphaView = v.findViewById(R.id.claim_thumbnail_alpha); + noThumbnailView = v.findViewById(R.id.claim_no_thumbnail); + thumbnailView = v.findViewById(R.id.claim_thumbnail); + vanityUrlView = v.findViewById(R.id.claim_vanity_url); + durationView = v.findViewById(R.id.claim_duration); + titleView = v.findViewById(R.id.claim_title); + publisherView = v.findViewById(R.id.claim_publisher); + publishTimeView = v.findViewById(R.id.claim_publish_time); + pendingTextView = v.findViewById(R.id.claim_pending_text); + repostInfoView = v.findViewById(R.id.claim_repost_info); + repostChannelView = v.findViewById(R.id.claim_repost_channel); + selectedOverlayView = v.findViewById(R.id.claim_selected_overlay); + fileSizeView = v.findViewById(R.id.claim_file_size); + downloadProgressView = v.findViewById(R.id.claim_download_progress); + deviceView = v.findViewById(R.id.claim_view_device); + } + } + + @Override + public int getItemCount() { + return items != null ? items.size() : 0; + } + + @Override + public int getItemViewType(int position) { + if (items.get(position).isFeatured()) { + return VIEW_TYPE_FEATURED; + } + + Claim claim = items.get(position); + String valueType = items.get(position).getValueType(); + Claim actualClaim = Claim.TYPE_REPOST.equalsIgnoreCase(valueType) ? claim.getRepostedClaim() : claim; + + return Claim.TYPE_CHANNEL.equalsIgnoreCase(actualClaim.getValueType()) ? VIEW_TYPE_CHANNEL : VIEW_TYPE_STREAM; + } + + public void updateFileForClaimByIdOrUrl(LbryFile file, String claimId, String url) { + updateFileForClaimByIdOrUrl(file, claimId, url, false); + } + public void updateFileForClaimByIdOrUrl(LbryFile file, String claimId, String url, boolean skipNotFound) { + if (!skipNotFound) { + if (notFoundClaimIdMap.containsKey(claimId) && notFoundClaimUrlMap.containsKey(url)) { + return; + } + } + if (quickClaimIdMap.containsKey(claimId)) { + quickClaimIdMap.get(claimId).setFile(file); + notifyDataSetChanged(); + return; + } + if (quickClaimUrlMap.containsKey(claimId)) { + quickClaimUrlMap.get(claimId).setFile(file); + notifyDataSetChanged(); + return; + } + + boolean claimFound = false; + for (int i = 0; i < items.size(); i++) { + Claim claim = items.get(i); + if (claimId.equalsIgnoreCase(claim.getClaimId()) || url.equalsIgnoreCase(claim.getPermanentUrl())) { + quickClaimIdMap.put(claimId, claim); + quickClaimUrlMap.put(url, claim); + claim.setFile(file); + notifyDataSetChanged(); + claimFound = true; + break; + } + } + + if (!claimFound) { + notFoundClaimIdMap.put(claimId, true); + notFoundClaimUrlMap.put(url, true); + } + } + public void clearFileForClaimOrUrl(String outpoint, String url) { + clearFileForClaimOrUrl(outpoint, url, false); + notifyDataSetChanged(); + } + + + public void clearFileForClaimOrUrl(String outpoint, String url, boolean remove) { + int claimIndex = -1; + for (int i = 0; i < items.size(); i++) { + Claim claim = items.get(i); + if (outpoint.equalsIgnoreCase(claim.getOutpoint()) || url.equalsIgnoreCase(claim.getPermanentUrl())) { + claimIndex = i; + claim.setFile(null); + break; + } + } + if (remove && claimIndex > -1) { + Claim removed = items.remove(claimIndex); + selectedItems.remove(removed); + } + + notifyDataSetChanged(); + } + + @Override + public ClaimListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + int viewResourceId = -1; + switch (viewType) { + case VIEW_TYPE_FEATURED: viewResourceId = R.layout.list_item_featured_search_result; break; + case VIEW_TYPE_CHANNEL: viewResourceId = R.layout.list_item_channel; break; + case VIEW_TYPE_STREAM: default: viewResourceId = R.layout.list_item_stream; break; + } + + View v = LayoutInflater.from(context).inflate(viewResourceId, parent, false); + return new ClaimListAdapter.ViewHolder(v); + } + + public int getScaledValue(int value) { + return (int) (value * scale + 0.5f); + } + + @Override + public void onBindViewHolder(ClaimListAdapter.ViewHolder vh, int position) { + int type = getItemViewType(position); + int paddingTop = position == 0 ? 16 : 8; + int paddingBottom = position == getItemCount() - 1 ? 16 : 8; + int paddingTopScaled = getScaledValue(paddingTop); + int paddingBottomScaled = getScaledValue(paddingBottom); + vh.itemView.setPadding(vh.itemView.getPaddingLeft(), paddingTopScaled, vh.itemView.getPaddingRight(), paddingBottomScaled); + + Claim original = items.get(position); + boolean isRepost = Claim.TYPE_REPOST.equalsIgnoreCase(original.getValueType()); + final Claim item = Claim.TYPE_REPOST.equalsIgnoreCase(original.getValueType()) ? original.getRepostedClaim() : original; + Claim.GenericMetadata metadata = item.getValue(); + Claim signingChannel = item.getSigningChannel(); + Claim.StreamMetadata streamMetadata = null; + if (metadata instanceof Claim.StreamMetadata) { + streamMetadata = (Claim.StreamMetadata) metadata; + } + String thumbnailUrl = item.getThumbnailUrl(); + long publishTime = (streamMetadata != null && streamMetadata.getReleaseTime() > 0) ? streamMetadata.getReleaseTime() * 1000 : item.getTimestamp() * 1000; + int bgColor = Helper.generateRandomColorForValue(item.getClaimId()); + if (bgColor == 0) { + bgColor = Helper.generateRandomColorForValue(item.getName()); + } + + boolean isPending = item.getConfirmations() == 0; + boolean isSelected = isClaimSelected(item); + vh.itemView.setSelected(isSelected); + vh.selectedOverlayView.setVisibility(isSelected ? View.VISIBLE : View.GONE); + vh.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (isPending) { + Snackbar.make(vh.itemView, R.string.item_pending_blockchain, Snackbar.LENGTH_LONG).show(); + return; + } + + if (inSelectionMode) { + toggleSelectedClaim(original); + } else { + if (listener != null) { + listener.onClaimClicked(item); + } + } + } + }); + vh.itemView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + if (!canEnterSelectionMode) { + return false; + } + + if (isPending) { + Snackbar.make(vh.itemView, R.string.item_pending_blockchain, Snackbar.LENGTH_LONG).show(); + return false; + } + + if (!inSelectionMode) { + inSelectionMode = true; + if (selectionModeListener != null) { + selectionModeListener.onEnterSelectionMode(); + } + } + toggleSelectedClaim(original); + return true; + } + }); + + vh.publisherView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null && signingChannel != null) { + listener.onClaimClicked(signingChannel); + } + } + }); + + vh.publishTimeView.setVisibility(!isPending ? View.VISIBLE : View.GONE); + vh.pendingTextView.setVisibility(isPending ? View.VISIBLE : View.GONE); + vh.repostInfoView.setVisibility(isRepost ? View.VISIBLE : View.GONE); + vh.repostChannelView.setText(isRepost ? original.getSigningChannel().getName() : null); + vh.repostChannelView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) { + listener.onClaimClicked(original.getSigningChannel()); + } + } + }); + + vh.titleView.setText(Helper.isNullOrEmpty(item.getTitle()) ? item.getName() : item.getTitle()); + if (type == VIEW_TYPE_FEATURED) { + LbryUri vanityUrl = new LbryUri(); + vanityUrl.setClaimName(item.getName()); + vh.vanityUrlView.setText(vanityUrl.toString()); + } + + vh.feeContainer.setVisibility(item.isUnresolved() || !Claim.TYPE_STREAM.equalsIgnoreCase(item.getValueType()) ? View.GONE : View.VISIBLE); + vh.noThumbnailView.setVisibility(Helper.isNullOrEmpty(thumbnailUrl) ? View.VISIBLE : View.GONE); + Helper.setIconViewBackgroundColor(vh.noThumbnailView, bgColor, false, context); + + if (type == VIEW_TYPE_FEATURED && item.isUnresolved()) { + vh.durationView.setVisibility(View.GONE); + vh.titleView.setText("Nothing here. Publish something!"); + vh.alphaView.setText(item.getName().substring(0, Math.min(5, item.getName().length() - 1))); + } else { + if (Claim.TYPE_STREAM.equalsIgnoreCase(item.getValueType())) { + long duration = item.getDuration(); + if (!Helper.isNullOrEmpty(thumbnailUrl)) { + Glide.with(context.getApplicationContext()). + load(thumbnailUrl). + centerCrop(). + placeholder(R.drawable.bg_thumbnail_placeholder). + into(vh.thumbnailView); + vh.thumbnailView.setVisibility(View.VISIBLE); + } else { + vh.thumbnailView.setVisibility(View.GONE); + } + + BigDecimal cost = item.getActualCost(Lbryio.LBCUSDRate); + vh.feeContainer.setVisibility(cost.doubleValue() > 0 && !hideFee ? View.VISIBLE : View.GONE); + vh.feeView.setText(cost.doubleValue() > 0 ? Helper.shortCurrencyFormat(cost.doubleValue()) : "Paid"); + vh.alphaView.setText(item.getName().substring(0, Math.min(5, item.getName().length() - 1))); + vh.publisherView.setText(signingChannel != null ? signingChannel.getName() : context.getString(R.string.anonymous)); + vh.publishTimeView.setText(DateUtils.getRelativeTimeSpanString( + publishTime, System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE)); + vh.durationView.setVisibility(duration > 0 ? View.VISIBLE : View.GONE); + vh.durationView.setText(Helper.formatDuration(duration)); + + LbryFile claimFile = item.getFile(); + boolean isDownloading = false; + int progress = 0; + String fileSizeString = claimFile == null ? null : Helper.formatBytes(claimFile.getTotalBytes(), false); + if (claimFile != null && + !Helper.isNullOrEmpty(claimFile.getDownloadPath()) && + !claimFile.isCompleted() && + claimFile.getWrittenBytes() < claimFile.getTotalBytes()) { + isDownloading = true; + progress = claimFile.getTotalBytes() > 0 ? + Double.valueOf(((double) claimFile.getWrittenBytes() / (double) claimFile.getTotalBytes()) * 100.0).intValue() : 0; + fileSizeString = String.format("%s / %s", + Helper.formatBytes(claimFile.getWrittenBytes(), false), + Helper.formatBytes(claimFile.getTotalBytes(), false)); + } + + Helper.setViewText(vh.fileSizeView, claimFile != null && !Helper.isNullOrEmpty(claimFile.getDownloadPath()) ? fileSizeString : null); + Helper.setViewVisibility(vh.downloadProgressView, isDownloading ? View.VISIBLE : View.INVISIBLE); + Helper.setViewProgress(vh.downloadProgressView, progress); + Helper.setViewText(vh.deviceView, item.getDevice()); + } else if (Claim.TYPE_CHANNEL.equalsIgnoreCase(item.getValueType())) { + if (!Helper.isNullOrEmpty(thumbnailUrl)) { + Glide.with(context.getApplicationContext()). + load(thumbnailUrl). + centerCrop(). + placeholder(R.drawable.bg_thumbnail_placeholder). + apply(RequestOptions.circleCropTransform()). + into(vh.thumbnailView); + } + vh.alphaView.setText(item.getName().substring(1, 2).toUpperCase()); + vh.publisherView.setText(item.getName()); + vh.publishTimeView.setText(DateUtils.getRelativeTimeSpanString( + publishTime, System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE)); + } + } + } + + private void toggleSelectedClaim(Claim claim) { + if (selectedItems.contains(claim)) { + selectedItems.remove(claim); + } else { + selectedItems.add(claim); + } + + if (selectionModeListener != null) { + selectionModeListener.onItemSelectionToggled(); + } + + if (selectedItems.size() == 0) { + inSelectionMode = false; + if (selectionModeListener != null) { + selectionModeListener.onExitSelectionMode(); + } + } + + notifyDataSetChanged(); + } + + public interface ClaimListItemListener { + void onClaimClicked(Claim claim); + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/EditorsChoiceItemAdapter.java b/app/src/main/java/io/lbry/browser/adapter/EditorsChoiceItemAdapter.java new file mode 100644 index 00000000..557751aa --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/EditorsChoiceItemAdapter.java @@ -0,0 +1,117 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.EditorsChoiceItem; +import io.lbry.browser.utils.Helper; +import lombok.Setter; + +public class EditorsChoiceItemAdapter extends RecyclerView.Adapter { + private static final int VIEW_TYPE_HEADER = 1; + private static final int VIEW_TYPE_CONTENT = 2; + + private Context context; + private List items; + @Setter + private EditorsChoiceItemListener listener; + + public EditorsChoiceItemAdapter(List items, Context context) { + this.context = context; + this.items = new ArrayList<>(items); + } + + public void addFeaturedItem(EditorsChoiceItem item) { + items.add(0, item); + notifyDataSetChanged(); + } + + public void addItems(List items) { + for (EditorsChoiceItem item : items) { + if (!this.items.contains(item)) { + this.items.add(item); + } + } + notifyDataSetChanged(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected ImageView thumbnailView; + protected TextView descriptionView; + protected TextView headerView; + protected TextView titleView; + protected View cardView; + + public ViewHolder(View v) { + super(v); + + cardView = v.findViewById(R.id.editors_choice_content_card); + descriptionView = v.findViewById(R.id.editors_choice_content_description); + titleView = v.findViewById(R.id.editors_choice_content_title); + + thumbnailView = v.findViewById(R.id.editors_choice_content_thumbnail); + headerView = v.findViewById(R.id.editors_choice_header_title); + } + } + + @Override + public int getItemCount() { + return items != null ? items.size() : 0; + } + + @Override + public int getItemViewType(int position) { + return items.get(position).isHeader() ? VIEW_TYPE_HEADER : VIEW_TYPE_CONTENT; + } + + @Override + public EditorsChoiceItemAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_editors_choice, parent, false); + return new EditorsChoiceItemAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(EditorsChoiceItemAdapter.ViewHolder vh, int position) { + int type = getItemViewType(position); + EditorsChoiceItem item = items.get(position); + + vh.headerView.setVisibility(type == VIEW_TYPE_HEADER ? View.VISIBLE : View.GONE); + vh.cardView.setVisibility(type == VIEW_TYPE_CONTENT ? View.VISIBLE : View.GONE); + + vh.headerView.setText(item.getTitle()); + vh.titleView.setText(item.getTitle()); + vh.descriptionView.setText(item.getDescription()); + if (!Helper.isNullOrEmpty(item.getThumbnailUrl())) { + Glide.with(context.getApplicationContext()). + load(item.getThumbnailUrl()). + centerCrop(). + placeholder(R.drawable.bg_thumbnail_placeholder). + into(vh.thumbnailView); + } + + vh.cardView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) { + listener.onEditorsChoiceItemClicked(item); + } + } + }); + } + + public interface EditorsChoiceItemListener { + void onEditorsChoiceItemClicked(EditorsChoiceItem item); + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/GalleryGridAdapter.java b/app/src/main/java/io/lbry/browser/adapter/GalleryGridAdapter.java new file mode 100644 index 00000000..b9edfcc0 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/GalleryGridAdapter.java @@ -0,0 +1,119 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.graphics.Rect; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.GalleryItem; +import io.lbry.browser.utils.Helper; +import lombok.Setter; + +public class GalleryGridAdapter extends RecyclerView.Adapter { + private Context context; + private List items; + @Setter + private GalleryItemClickListener listener; + + public GalleryGridAdapter(List items, Context context) { + this.items = new ArrayList<>(items); + this.context = context; + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected ImageView thumbnailView; + protected TextView durationView; + public ViewHolder(View v) { + super(v); + thumbnailView = v.findViewById(R.id.gallery_item_thumbnail); + durationView = v.findViewById(R.id.gallery_item_duration); + } + } + + public int getItemCount() { + return items != null ? items.size() : 0; + } + + public void addItem(GalleryItem item) { + if (!items.contains(item)) { + items.add(item); + notifyDataSetChanged(); + } + } + + public void addItems(List items) { + for (GalleryItem item : items) { + if (!this.items.contains(item)) { + this.items.add(item); + notifyDataSetChanged(); + } + } + } + + public void clearItems() { + items.clear(); + notifyDataSetChanged(); + } + + @Override + public GalleryGridAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_gallery, root, false); + return new GalleryGridAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(GalleryGridAdapter.ViewHolder vh, int position) { + GalleryItem item = items.get(position); + String thumbnailUrl = item.getThumbnailPath(); + Glide.with(context.getApplicationContext()).load(thumbnailUrl).centerCrop().into(vh.thumbnailView); + vh.durationView.setVisibility(item.getDuration() > 0 ? View.VISIBLE : View.INVISIBLE); + vh.durationView.setText(item.getDuration() > 0 ? Helper.formatDuration(Double.valueOf(item.getDuration() / 1000.0).longValue()) : null); + + vh.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) { + listener.onGalleryItemClicked(item); + } + } + }); + } + + public interface GalleryItemClickListener { + void onGalleryItemClicked(GalleryItem item); + } + + public static class GalleryGridItemDecoration extends RecyclerView.ItemDecoration { + + private int spanCount; + private int spacing; + + public GalleryGridItemDecoration(int spanCount, int spacing) { + this.spanCount = spanCount; + this.spacing = spacing; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + int position = parent.getChildAdapterPosition(view); // item position + int column = position % spanCount; // item column + + outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing) + outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing) + if (position >= spanCount) { + outRect.top = spacing; // item top + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/InlineChannelSpinnerAdapter.java b/app/src/main/java/io/lbry/browser/adapter/InlineChannelSpinnerAdapter.java new file mode 100644 index 00000000..9924a67a --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/InlineChannelSpinnerAdapter.java @@ -0,0 +1,80 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.database.DataSetObserver; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.SpinnerAdapter; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.Claim; + +public class InlineChannelSpinnerAdapter extends ArrayAdapter { + + private List channels; + private int layoutResourceId; + private LayoutInflater inflater; + + public InlineChannelSpinnerAdapter(Context context, int resource, List channels) { + super(context, resource, 0, channels); + inflater = LayoutInflater.from(context); + layoutResourceId = resource; + this.channels = new ArrayList<>(channels); + } + public void addPlaceholder(boolean includeAnonymous) { + Claim placeholder = new Claim(); + placeholder.setPlaceholder(true); + insert(placeholder, 0); + channels.add(0, placeholder); + + if (includeAnonymous) { + Claim anonymous = new Claim(); + anonymous.setPlaceholderAnonymous(true); + insert(anonymous, 1); + channels.add(1, anonymous); + } + } + + public int getItemPosition(Claim item) { + for (int i = 0; i < channels.size(); i++) { + Claim channel = channels.get(i); + if (item.getClaimId().equalsIgnoreCase(channel.getClaimId())) { + return i; + } + } + return -1; + } + + @Override + public View getDropDownView(int position, View view, ViewGroup parent) { + return createView(position, view, parent); + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + return createView(position, view, parent); + } + private View createView(int position, View convertView, ViewGroup parent){ + View view = inflater.inflate(layoutResourceId, parent, false); + + Context context = getContext(); + Claim channel = getItem(position); + String name = channel.getName(); + if (channel.isPlaceholder()) { + name = context.getString(R.string.create_a_channel); + } else if (channel.isPlaceholderAnonymous()) { + name = context.getString(R.string.anonymous); + } + + TextView label = view.findViewById(R.id.channel_item_name); + label.setText(name); + + return view; + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/InviteeListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/InviteeListAdapter.java new file mode 100644 index 00000000..7ff066b0 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/InviteeListAdapter.java @@ -0,0 +1,92 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Typeface; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.lbryinc.Invitee; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryUri; +import lombok.Setter; + +public class InviteeListAdapter extends RecyclerView.Adapter { + + private Context context; + private List items; + + public InviteeListAdapter(List invitees, Context context) { + this.context = context; + this.items = new ArrayList<>(invitees); + } + + public void clear() { + items.clear(); + notifyDataSetChanged(); + } + + public List getItems() { + return new ArrayList<>(items); + } + + public void addHeader() { + Invitee header = new Invitee(); + header.setHeader(true); + items.add(0, header); + } + + public void addInvitees(List Invitees) { + for (Invitee tx : Invitees) { + if (!items.contains(tx)) { + items.add(tx); + } + } + notifyDataSetChanged(); + } + + public int getItemCount() { + return items != null ? items.size() : 0; + } + + @Override + public InviteeListAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_invitee, root, false); + return new InviteeListAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(InviteeListAdapter.ViewHolder vh, int position) { + Invitee item = items.get(position); + vh.emailView.setText(item.isHeader() ? context.getString(R.string.email) : item.getEmail()); + vh.emailView.setTypeface(null, item.isHeader() ? Typeface.BOLD : Typeface.NORMAL); + + String rewardText = context.getString( + item.isInviteRewardClaimed() ? R.string.claimed : + (item.isInviteRewardClaimable() ? R.string.claimable : R.string.unclaimable)); + vh.rewardView.setText(item.isHeader() ? context.getString(R.string.reward) : rewardText); + vh.rewardView.setTypeface(null, item.isHeader() ? Typeface.BOLD : Typeface.NORMAL); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected TextView emailView; + protected TextView rewardView; + + public ViewHolder(View v) { + super(v); + emailView = v.findViewById(R.id.invitee_email); + rewardView = v.findViewById(R.id.invitee_reward); + } + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/LanguageSpinnerAdapter.java b/app/src/main/java/io/lbry/browser/adapter/LanguageSpinnerAdapter.java new file mode 100644 index 00000000..1e5c9a44 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/LanguageSpinnerAdapter.java @@ -0,0 +1,53 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import io.lbry.browser.R; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.Language; +import io.lbry.browser.utils.Predefined; + +public class LanguageSpinnerAdapter extends ArrayAdapter { + private int layoutResourceId; + private LayoutInflater inflater; + + public LanguageSpinnerAdapter(Context context, int resource) { + super(context, resource, 0, Predefined.PUBLISH_LANGUAGES); + inflater = LayoutInflater.from(context); + layoutResourceId = resource; + } + + public int getItemPosition(String languageCode) { + for (int i = 0; i < Predefined.PUBLISH_LANGUAGES.size(); i++) { + Language lang = Predefined.PUBLISH_LANGUAGES.get(i); + if (lang.getCode().equalsIgnoreCase(languageCode)) { + return i; + } + } + return -1; + } + + @Override + public View getDropDownView(int position, View view, @NonNull ViewGroup parent) { + return createView(position, view, parent); + } + @Override + public View getView(int position, View view, @NonNull ViewGroup parent) { + return createView(position, view, parent); + } + private View createView(int position, View convertView, ViewGroup parent) { + Language item = getItem(position); + View view = inflater.inflate(layoutResourceId, parent, false); + TextView label = view.findViewById(R.id.item_display_name); + label.setText(item != null ? item.getStringResourceId() : 0); + + return view; + } +} \ No newline at end of file diff --git a/app/src/main/java/io/lbry/browser/adapter/LicenseSpinnerAdapter.java b/app/src/main/java/io/lbry/browser/adapter/LicenseSpinnerAdapter.java new file mode 100644 index 00000000..c6776cf9 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/LicenseSpinnerAdapter.java @@ -0,0 +1,52 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import io.lbry.browser.R; +import io.lbry.browser.model.Language; +import io.lbry.browser.model.License; +import io.lbry.browser.utils.Predefined; + +public class LicenseSpinnerAdapter extends ArrayAdapter { + private int layoutResourceId; + private LayoutInflater inflater; + + public LicenseSpinnerAdapter(Context context, int resource) { + super(context, resource, 0, Predefined.LICENSES); + inflater = LayoutInflater.from(context); + layoutResourceId = resource; + } + public int getItemPosition(String name) { + for (int i = 0; i < Predefined.LICENSES.size(); i++) { + License lic = Predefined.LICENSES.get(i); + if (lic.getName().equalsIgnoreCase(name)) { + return i; + } + } + return -1; + } + + @Override + public View getDropDownView(int position, View view, @NonNull ViewGroup parent) { + return createView(position, view, parent); + } + @Override + public View getView(int position, View view, @NonNull ViewGroup parent) { + return createView(position, view, parent); + } + private View createView(int position, View convertView, ViewGroup parent) { + License item = getItem(position); + View view = inflater.inflate(layoutResourceId, parent, false); + TextView label = view.findViewById(R.id.item_display_name); + label.setText(item != null ? item.getStringResourceId() : 0); + + return view; + } +} \ No newline at end of file diff --git a/app/src/main/java/io/lbry/browser/adapter/NavigationMenuAdapter.java b/app/src/main/java/io/lbry/browser/adapter/NavigationMenuAdapter.java new file mode 100644 index 00000000..f5c75a55 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/NavigationMenuAdapter.java @@ -0,0 +1,117 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.NavMenuItem; +import io.lbry.browser.ui.controls.SolidIconView; +import io.lbry.browser.utils.Helper; +import lombok.Getter; +import lombok.Setter; + +public class NavigationMenuAdapter extends RecyclerView.Adapter { + private static final int TYPE_GROUP = 1; + private static final int TYPE_ITEM = 2; + + private Context context; + private List menuItems; + private NavMenuItem currentItem; + @Setter + private NavigationMenuItemClickListener listener; + + public NavigationMenuAdapter(List menuItems, Context context) { + this.menuItems = new ArrayList<>(menuItems); + this.context = context; + } + + public void setCurrentItem(int id) { + for (NavMenuItem item : menuItems) { + if (item.getId() == id) { + this.currentItem = item; + break; + } + } + notifyDataSetChanged(); + } + + public void setExtraLabelForItem(int id, String extraLabel) { + for (NavMenuItem item : menuItems) { + if (item.getId() == id) { + item.setExtraLabel(extraLabel); + break; + } + } + notifyDataSetChanged(); + } + + public void setCurrentItem(NavMenuItem currentItem) { + this.currentItem = currentItem; + notifyDataSetChanged(); + } + + public int getCurrentItemId() { + return currentItem != null ? currentItem.getId() : -1; + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected SolidIconView iconView; + protected TextView titleView; + public ViewHolder(View v) { + super(v); + titleView = v.findViewById(R.id.nav_menu_title); + iconView = v.findViewById(R.id.nav_menu_item_icon); + } + } + + @Override + public int getItemCount() { + return menuItems != null ? menuItems.size() : 0; + } + + @Override + public int getItemViewType(int position) { + return menuItems.get(position).isGroup() ? TYPE_GROUP : TYPE_ITEM; + } + + @Override + public NavigationMenuAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(context).inflate(viewType == TYPE_GROUP ? + R.layout.list_item_nav_menu_group : R.layout.list_item_nav_menu_item, parent, false); + return new NavigationMenuAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(ViewHolder vh, int position) { + int type = getItemViewType(position); + NavMenuItem item = menuItems.get(position); + String displayTitle = !Helper.isNullOrEmpty(item.getExtraLabel()) ? String.format("%s (%s)", item.getTitle(), item.getExtraLabel()) : item.getTitle(); + vh.titleView.setText(displayTitle); + if (type == TYPE_ITEM && vh.iconView != null) { + vh.iconView.setText(item.getIcon()); + } + vh.itemView.setSelected(item.equals(currentItem)); + vh.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) { + listener.onNavigationMenuItemClicked(item); + } + } + }); + } + + public interface NavigationMenuItemClickListener { + void onNavigationMenuItemClicked(NavMenuItem menuItem); + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/RewardListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/RewardListAdapter.java new file mode 100644 index 00000000..f3895452 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/RewardListAdapter.java @@ -0,0 +1,216 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.net.Uri; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.lbryinc.Reward; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; +import lombok.Getter; +import lombok.Setter; + +public class RewardListAdapter extends RecyclerView.Adapter { + + public static final int DISPLAY_MODE_ALL = 1; + public static final int DISPLAY_MODE_UNCLAIMED = 2; + + private Context context; + @Setter + private List all; + private List items; + @Setter + private RewardClickListener clickListener; + @Getter + private int displayMode; + + public RewardListAdapter(List all, Context context) { + this.all = new ArrayList<>(all); + this.items = new ArrayList<>(all); + this.context = context; + this.displayMode = DISPLAY_MODE_ALL; + + addCustomReward(); + } + + public void setRewards(List rewards) { + this.all = new ArrayList<>(rewards); + updateItemsForDisplayMode(); + notifyDataSetChanged(); + } + + public void setDisplayMode(int displayMode) { + this.displayMode = displayMode; + updateItemsForDisplayMode(); + notifyDataSetChanged(); + } + + private void updateItemsForDisplayMode() { + if (displayMode == DISPLAY_MODE_ALL) { + items = new ArrayList<>(all); + } else if (displayMode == DISPLAY_MODE_UNCLAIMED) { + items = new ArrayList<>(); + for (Reward reward : all) { + if (!reward.isClaimed()) { + items.add(reward); + } + } + } + addCustomReward(); + } + + private void addCustomReward() { + Reward custom = new Reward(); + custom.setCustom(true); + custom.setRewardTitle(context.getString(R.string.custom_reward_title)); + custom.setRewardDescription(context.getString(R.string.custom_reward_description)); + items.add(custom); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected View iconClaimed; + protected View loading; + protected View upTo; + protected TextView textTitle; + protected TextView textDescription; + protected TextView textLbcValue; + protected TextView textUsdValue; + protected TextView textLinkTransaction; + protected EditText inputCustomCode; + protected MaterialButton buttonClaimCustom; + public ViewHolder(View v) { + super(v); + iconClaimed = v.findViewById(R.id.reward_item_claimed_icon); + upTo = v.findViewById(R.id.reward_item_up_to); + loading = v.findViewById(R.id.reward_item_loading); + textTitle = v.findViewById(R.id.reward_item_title); + textDescription = v.findViewById(R.id.reward_item_description); + textLbcValue = v.findViewById(R.id.reward_item_lbc_value); + textLinkTransaction = v.findViewById(R.id.reward_item_tx_link); + textUsdValue = v.findViewById(R.id.reward_item_usd_value); + inputCustomCode = v.findViewById(R.id.reward_item_custom_code_input); + buttonClaimCustom = v.findViewById(R.id.reward_item_custom_claim_button); + } + } + + public int getItemCount() { + return items != null ? items.size() : 0; + } + + public void addReward(Reward reward) { + if (!items.contains(reward)) { + items.add(reward); + } + notifyDataSetChanged(); + } + public List getRewards() { + return new ArrayList<>(items); + } + + @Override + public RewardListAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_reward, root, false); + return new RewardListAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(RewardListAdapter.ViewHolder vh, int position) { + Reward reward = items.get(position); + String displayAmount = reward.getDisplayAmount(); + double rewardAmount = 0; + if (!"?".equals(displayAmount)) { + rewardAmount = Double.valueOf(displayAmount); + } + boolean hasTransaction = !Helper.isNullOrEmpty(reward.getTransactionId()) && reward.getTransactionId().length() > 7; + vh.iconClaimed.setVisibility(reward.isClaimed() ? View.VISIBLE : View.INVISIBLE); + vh.inputCustomCode.setVisibility(reward.isCustom() ? View.VISIBLE : View.GONE); + vh.buttonClaimCustom.setVisibility(reward.isCustom() ? View.VISIBLE : View.GONE); + vh.textTitle.setText(reward.getRewardTitle()); + vh.textDescription.setText(reward.getRewardDescription()); + vh.upTo.setVisibility(reward.shouldDisplayRange() ? View.VISIBLE : View.GONE); + vh.textLbcValue.setText(reward.isCustom() ? "?" : Helper.LBC_CURRENCY_FORMAT.format(Helper.parseDouble(reward.getDisplayAmount(), 0))); + vh.textLinkTransaction.setVisibility(hasTransaction ? View.VISIBLE : View.GONE); + vh.textLinkTransaction.setText(hasTransaction ? reward.getTransactionId().substring(0, 7) : null); + vh.textLinkTransaction.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (context != null) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("%s/%s", Helper.EXPLORER_TX_PREFIX, reward.getTransactionId()))); + context.startActivity(intent); + } + } + }); + + vh.textUsdValue.setText(reward.isCustom() || Lbryio.LBCUSDRate == 0 ? null : + String.format("≈$%s", Helper.SIMPLE_CURRENCY_FORMAT.format(rewardAmount * Lbryio.LBCUSDRate))); + + vh.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (reward.isClaimed()) { + return; + } + + if (clickListener != null) { + clickListener.onRewardClicked(reward, vh.loading); + } + } + }); + + vh.inputCustomCode.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + String value = charSequence.toString().trim(); + vh.buttonClaimCustom.setEnabled(value.length() > 0); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + + vh.buttonClaimCustom.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String claimCode = Helper.getValue(vh.inputCustomCode.getText()); + if (Helper.isNullOrEmpty(claimCode)) { + Snackbar.make(view, R.string.please_enter_claim_code, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + return; + } + + if (clickListener != null) { + clickListener.onCustomClaimButtonClicked(claimCode, vh.inputCustomCode, vh.buttonClaimCustom, vh.loading); + } + } + }); + } + + public interface RewardClickListener { + void onRewardClicked(Reward reward, View loadingView); + void onCustomClaimButtonClicked(String code, EditText inputCustomCode, MaterialButton buttonClaim, View loadingView); + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/SuggestedChannelGridAdapter.java b/app/src/main/java/io/lbry/browser/adapter/SuggestedChannelGridAdapter.java new file mode 100644 index 00000000..ff152d3f --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/SuggestedChannelGridAdapter.java @@ -0,0 +1,127 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.Claim; +import io.lbry.browser.listener.ChannelItemSelectionListener; +import io.lbry.browser.utils.Helper; +import lombok.Setter; + +public class SuggestedChannelGridAdapter extends RecyclerView.Adapter { + private Context context; + private List items; + private List selectedItems; + @Setter + private ChannelItemSelectionListener listener; + + public SuggestedChannelGridAdapter(List items, Context context) { + this.items = new ArrayList<>(items); + this.selectedItems = new ArrayList<>(); + this.context = context; + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected View noThumbnailView; + protected ImageView thumbnailView; + protected TextView alphaView; + protected TextView titleView; + protected TextView tagView; + public ViewHolder(View v) { + super(v); + noThumbnailView = v.findViewById(R.id.suggested_channel_no_thumbnail); + alphaView = v.findViewById(R.id.suggested_channel_alpha_view); + thumbnailView = v.findViewById(R.id.suggested_channel_thumbnail); + titleView = v.findViewById(R.id.suggested_channel_title); + tagView = v.findViewById(R.id.suggested_channel_tag); + } + } + + public int getItemCount() { + return items != null ? items.size() : 0; + } + + public int getSelectedCount() { return selectedItems.size(); } + + public void clearItems() { + items.clear(); + notifyDataSetChanged(); + } + + public List getSelectedItems() { + return this.selectedItems; + } + public void clearSelectedItems() { + this.selectedItems.clear(); + } + public boolean isClaimSelected(Claim claim) { + return selectedItems.contains(claim); + } + + public void addClaims(List claims) { + for (Claim claim : claims) { + if (!items.contains(claim)) { + items.add(claim); + } + } + notifyDataSetChanged(); + } + + @Override + public SuggestedChannelGridAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_suggested_channel, root, false); + return new SuggestedChannelGridAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(SuggestedChannelGridAdapter.ViewHolder vh, int position) { + Claim claim = items.get(position); + String thumbnailUrl = claim.getThumbnailUrl(); + + int bgColor = Helper.generateRandomColorForValue(claim.getClaimId()); + Helper.setIconViewBackgroundColor(vh.noThumbnailView, bgColor, false, context); + vh.noThumbnailView.setVisibility(Helper.isNullOrEmpty(thumbnailUrl) ? View.INVISIBLE : View.VISIBLE); + vh.alphaView.setText(claim.getName().substring(1, 2)); + if (!Helper.isNullOrEmpty(thumbnailUrl)) { + vh.thumbnailView.setVisibility(View.VISIBLE); + Glide.with(context.getApplicationContext()).load(thumbnailUrl).apply(RequestOptions.circleCropTransform()).into(vh.thumbnailView); + } else { + vh.thumbnailView.setVisibility(View.GONE); + } + vh.titleView.setText(Helper.isNullOrEmpty(claim.getTitle()) ? claim.getName() : claim.getTitle()); + + String firstTag = claim.getFirstTag(); + vh.tagView.setVisibility(Helper.isNullOrEmpty(firstTag) ? View.INVISIBLE : View.VISIBLE); + vh.tagView.setBackgroundResource(R.drawable.bg_tag); + vh.tagView.setText(firstTag); + vh.itemView.setSelected(isClaimSelected(claim)); + + vh.itemView.setOnClickListener(view -> { + if (selectedItems.contains(claim)) { + selectedItems.remove(claim); + if (listener != null) { + listener.onChannelItemDeselected(claim); + } + } else { + selectedItems.add(claim); + if (listener != null) { + listener.onChannelItemSelected(claim); + } + } + notifyDataSetChanged(); + }); + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/TagListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/TagListAdapter.java new file mode 100644 index 00000000..3ed25b93 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/TagListAdapter.java @@ -0,0 +1,108 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.Tag; +import lombok.Getter; +import lombok.Setter; + +public class TagListAdapter extends RecyclerView.Adapter { + + public static final int CUSTOMIZE_MODE_NONE = 0; + public static final int CUSTOMIZE_MODE_ADD = 1; + public static final int CUSTOMIZE_MODE_REMOVE = 2; + + private Context context; + private List items; + @Setter + private TagClickListener clickListener; + @Getter + @Setter + private int customizeMode; + + public TagListAdapter(List tags, Context context) { + this.context = context; + this.items = new ArrayList<>(tags); + this.customizeMode = CUSTOMIZE_MODE_NONE; + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected ImageView iconView; + protected TextView nameView; + public ViewHolder(View v) { + super(v); + iconView = v.findViewById(R.id.tag_action); + nameView = v.findViewById(R.id.tag_name); + } + } + + public int getItemCount() { + return items != null ? items.size() : 0; + } + + public void addTag(Tag tag) { + if (!items.contains(tag)) { + items.add(tag); + } + notifyDataSetChanged(); + } + public List getTags() { + return new ArrayList<>(items); + } + + public void setTags(List tags) { + items = new ArrayList<>(tags); + notifyDataSetChanged(); + } + + public void addTags(List tags) { + for (Tag tag : tags) { + if (!items.contains(tag)) { + items.add(tag); + } + } + notifyDataSetChanged(); + } + public void removeTag(Tag tag) { + items.remove(tag); + notifyDataSetChanged(); + } + + @Override + public TagListAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_tag, root, false); + return new TagListAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(TagListAdapter.ViewHolder vh, int position) { + Tag tag = items.get(position); + vh.nameView.setText(tag.getName().toLowerCase()); + vh.iconView.setVisibility(customizeMode == CUSTOMIZE_MODE_NONE ? View.GONE : View.VISIBLE); + vh.iconView.setImageResource(customizeMode == CUSTOMIZE_MODE_REMOVE ? R.drawable.ic_close : R.drawable.ic_add); + vh.itemView.setBackgroundResource(tag.isMature() ? R.drawable.bg_tag_mature : R.drawable.bg_tag); + vh.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (clickListener != null) { + clickListener.onTagClicked(tag, customizeMode); + } + } + }); + } + + public interface TagClickListener { + void onTagClicked(Tag tag, int customizeMode); + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/TransactionListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/TransactionListAdapter.java new file mode 100644 index 00000000..95b4a139 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/TransactionListAdapter.java @@ -0,0 +1,132 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.model.Transaction; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryUri; +import lombok.Setter; + +public class TransactionListAdapter extends RecyclerView.Adapter { + + private static final DecimalFormat TX_LIST_AMOUNT_FORMAT = new DecimalFormat("#,##0.0000"); + private static final SimpleDateFormat TX_LIST_DATE_FORMAT = new SimpleDateFormat("MMM d"); + + private Context context; + private List items; + @Setter + private TransactionClickListener listener; + + public TransactionListAdapter(List transactions, Context context) { + this.context = context; + this.items = new ArrayList<>(transactions); + } + + public void clear() { + items.clear(); + notifyDataSetChanged(); + } + + public List getItems() { + return new ArrayList<>(items); + } + + public void addTransactions(List transactions) { + for (Transaction tx : transactions) { + if (!items.contains(tx)) { + items.add(tx); + } + } + notifyDataSetChanged(); + } + + public int getItemCount() { + return items != null ? items.size() : 0; + } + + @Override + public TransactionListAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_transaction, root, false); + return new TransactionListAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(TransactionListAdapter.ViewHolder vh, int position) { + Transaction item = items.get(position); + vh.descView.setText(item.getDescriptionStringId()); + vh.amountView.setText(TX_LIST_AMOUNT_FORMAT.format(item.getValue().doubleValue())); + vh.claimView.setText(item.getClaim()); + vh.feeView.setText(context.getString(R.string.tx_list_fee, TX_LIST_AMOUNT_FORMAT.format(item.getFee().doubleValue()))); + vh.txidLinkView.setText(item.getTxid().substring(0, 7)); + vh.dateView.setText(TX_LIST_DATE_FORMAT.format(item.getTxDate())); + + vh.infoFeeContainer.setVisibility(!Helper.isNullOrEmpty(item.getClaim()) || Math.abs(item.getFee().doubleValue()) > 0 ? + View.VISIBLE : View.GONE); + + vh.claimView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + LbryUri claimUrl = item.getClaimUrl(); + if (claimUrl != null && listener != null) { + listener.onClaimUrlClicked(claimUrl); + } + } + }); + + vh.txidLinkView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (context != null) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("%s/%s", Helper.EXPLORER_TX_PREFIX, item.getTxid()))); + context.startActivity(intent); + } + } + }); + + vh.itemView.setOnClickListener(view -> { + if (listener != null) { + listener.onTransactionClicked(item); + } + }); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected TextView descView; + protected TextView amountView; + protected TextView claimView; + protected TextView feeView; + protected TextView txidLinkView; + protected TextView dateView; + protected View infoFeeContainer; + + public ViewHolder(View v) { + super(v); + descView = v.findViewById(R.id.transaction_desc); + amountView = v.findViewById(R.id.transaction_amount); + claimView = v.findViewById(R.id.transaction_claim); + feeView = v.findViewById(R.id.transaction_fee); + txidLinkView = v.findViewById(R.id.transaction_id_link); + dateView = v.findViewById(R.id.transaction_date); + infoFeeContainer = v.findViewById(R.id.transaction_info_fee_container); + } + } + + public interface TransactionClickListener { + void onTransactionClicked(Transaction transaction); + void onClaimUrlClicked(LbryUri uri); + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java new file mode 100644 index 00000000..cc2a09ea --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java @@ -0,0 +1,147 @@ +package io.lbry.browser.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.UrlSuggestion; +import io.lbry.browser.ui.controls.SolidIconView; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryUri; +import lombok.Setter; + +public class UrlSuggestionListAdapter extends RecyclerView.Adapter { + private Context context; + private List items; + @Setter + private UrlSuggestionClickListener listener; + + public UrlSuggestionListAdapter(Context context) { + this.context = context; + this.items = new ArrayList<>(); + } + + public void clear() { + items.clear(); + notifyDataSetChanged(); + } + + public List getItems() { + return new ArrayList<>(items); + } + + public List getItemUrls() { + List uris = new ArrayList<>(); + for (int i = 0; i < items.size(); i++) { + LbryUri uri = items.get(i).getUri(); + if (uri != null) { + uris.add(uri.toString()); + } + } + return uris; + } + + public void setClaimForUrl(LbryUri url, Claim claim) { + for (int i = 0; i < items.size(); i++) { + LbryUri thisUrl = items.get(i).getUri(); + try { + if (thisUrl != null) { + LbryUri vanity = LbryUri.parse(thisUrl.toVanityString()); + if (thisUrl.equals(url) || vanity.equals(url)) { + items.get(i).setClaim(claim); + } + } + } catch (LbryUriException ex) { + // pass + } + } + } + + public void addUrlSuggestions(List urlSuggestions) { + for (UrlSuggestion urlSuggestion : urlSuggestions) { + if (!items.contains(urlSuggestion)) { + items.add(urlSuggestion); + } + } + notifyDataSetChanged(); + } + + public int getItemCount() { + return items != null ? items.size() : 0; + } + + @Override + public UrlSuggestionListAdapter.ViewHolder onCreateViewHolder(ViewGroup root, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_url_suggestion, root, false); + return new UrlSuggestionListAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(UrlSuggestionListAdapter.ViewHolder vh, int position) { + UrlSuggestion item = items.get(position); + + String fullTitle, desc; + int iconStringId; + switch (item.getType()) { + case UrlSuggestion.TYPE_CHANNEL: + iconStringId = R.string.fa_at; + fullTitle = item.getTitle(); + desc = item.getClaim() != null ? item.getClaim().getTitle() : + ((item.isUseTextAsDescription() && !Helper.isNullOrEmpty(item.getText())) ? item.getText() : String.format(context.getString(R.string.view_channel_url_desc), item.getText())); + break; + case UrlSuggestion.TYPE_TAG: + iconStringId = R.string.fa_hashtag; + fullTitle = String.format(context.getString(R.string.tag_url_title), item.getText()); + desc = String.format(context.getString(R.string.explore_tag_url_desc), item.getText()); + break; + case UrlSuggestion.TYPE_SEARCH: + iconStringId = R.string.fa_search; + fullTitle = String.format(context.getString(R.string.search_url_title), item.getText()); + desc = String.format(context.getString(R.string.search_url_desc), item.getText()); + break; + case UrlSuggestion.TYPE_FILE: + default: + iconStringId = R.string.fa_file; + fullTitle = item.getTitle(); + desc = item.getClaim() != null ? item.getClaim().getTitle() : + ((item.isUseTextAsDescription() && !Helper.isNullOrEmpty(item.getText())) ? item.getText() : String.format(context.getString(R.string.view_file_url_desc), item.getText())); + break; + } + + vh.iconView.setText(iconStringId); + vh.titleView.setText(fullTitle); + vh.descView.setText(desc); + + vh.itemView.setOnClickListener(view -> { + if (listener != null) { + listener.onUrlSuggestionClicked(item); + } + }); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected SolidIconView iconView; + protected TextView titleView; + protected TextView descView; + public ViewHolder(View v) { + super(v); + iconView = v.findViewById(R.id.url_suggestion_icon); + titleView = v.findViewById(R.id.url_suggestion_title); + descView = v.findViewById(R.id.url_suggestion_description); + } + } + + public interface UrlSuggestionClickListener { + void onUrlSuggestionClicked(UrlSuggestion urlSuggestion); + } +} diff --git a/app/src/main/java/io/lbry/browser/adapter/VerificationPagerAdapter.java b/app/src/main/java/io/lbry/browser/adapter/VerificationPagerAdapter.java new file mode 100644 index 00000000..8569038c --- /dev/null +++ b/app/src/main/java/io/lbry/browser/adapter/VerificationPagerAdapter.java @@ -0,0 +1,71 @@ +package io.lbry.browser.adapter; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import io.lbry.browser.listener.SignInListener; +import io.lbry.browser.listener.WalletSyncListener; +import io.lbry.browser.ui.verification.EmailVerificationFragment; +import io.lbry.browser.ui.verification.ManualVerificationFragment; +import io.lbry.browser.ui.verification.PhoneVerificationFragment; +import io.lbry.browser.ui.verification.WalletVerificationFragment; +import lombok.SneakyThrows; + +/** + * 4 fragments + * - Email collect / verify (sign in) + * - Phone number collect / verify (rewards) + * - Wallet password + * - Manual verification page + */ +public class VerificationPagerAdapter extends FragmentStateAdapter { + public static final int PAGE_VERIFICATION_EMAIL = 0; + public static final int PAGE_VERIFICATION_PHONE = 1; + public static final int PAGE_VERIFICATION_WALLET = 2; + public static final int PAGE_VERIFICATION_MANUAL = 3; + + private FragmentActivity activity; + + public VerificationPagerAdapter(FragmentActivity activity) { + super(activity); + this.activity = activity; + } + + @SneakyThrows + @Override + public Fragment createFragment(int position) { + switch (position) { + case 0: + default: + EmailVerificationFragment evFragment = EmailVerificationFragment.class.newInstance(); + if (activity instanceof SignInListener) { + evFragment.setListener((SignInListener) activity); + } + return evFragment; + case 1: + PhoneVerificationFragment pvFragment = PhoneVerificationFragment.class.newInstance(); + if (activity instanceof SignInListener) { + pvFragment.setListener((SignInListener) activity); + } + return pvFragment; + case 2: + WalletVerificationFragment wvFragment = WalletVerificationFragment.class.newInstance(); + if (activity instanceof WalletSyncListener) { + wvFragment.setListener((WalletSyncListener) activity); + } + return wvFragment; + case 3: + ManualVerificationFragment mvFragment = ManualVerificationFragment.class.newInstance(); + if (activity instanceof SignInListener) { + mvFragment.setListener((SignInListener) activity); + } + return mvFragment; + } + } + + @Override + public int getItemCount() { + return 4; + } +} diff --git a/app/src/main/java/io/lbry/browser/data/DatabaseHelper.java b/app/src/main/java/io/lbry/browser/data/DatabaseHelper.java new file mode 100644 index 00000000..2087a7ff --- /dev/null +++ b/app/src/main/java/io/lbry/browser/data/DatabaseHelper.java @@ -0,0 +1,250 @@ +package io.lbry.browser.data; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.opengl.Visibility; + +import java.math.BigDecimal; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.model.Tag; +import io.lbry.browser.model.UrlSuggestion; +import io.lbry.browser.model.ViewHistory; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryUri; + +public class DatabaseHelper extends SQLiteOpenHelper { + public static final int DATABASE_VERSION = 2; + public static final String DATABASE_NAME = "LbryApp.db"; + private static DatabaseHelper instance; + + private static final String[] SQL_CREATE_TABLES = { + // local subscription store + "CREATE TABLE subscriptions (url TEXT PRIMARY KEY NOT NULL, channel_name TEXT NOT NULL)", + // url entry / suggestion history + "CREATE TABLE url_history (id INTEGER PRIMARY KEY NOT NULL, value TEXT NOT NULL, url TEXT, type INTEGER NOT NULL, timestamp TEXT NOT NULL)", + // tags (known and followed) + "CREATE TABLE tags (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, is_followed INTEGER NOT NULL)", + // view history (stores only stream claims that have resolved) + "CREATE TABLE view_history (" + + " id INTEGER PRIMARY KEY NOT NULL" + + ", url TEXT NOT NULL" + + ", claim_id TEXT" + + ", claim_name TEXT" + + ", cost REAL " + + ", currency TEXT " + + ", title TEXT " + + ", publisher_claim_id TEXT" + + ", publisher_name TEXT" + + ", publisher_title TEXT" + + ", thumbnail_url TEXT" + + ", release_time INTEGER " + + ", device TEXT" + + ", timestamp TEXT NOT NULL)" + }; + private static final String[] SQL_CREATE_INDEXES = { + "CREATE UNIQUE INDEX idx_subscription_url ON subscriptions (url)", + "CREATE UNIQUE INDEX idx_url_history_value ON url_history (value)", + "CREATE UNIQUE INDEX idx_url_history_url ON url_history (url)", + "CREATE UNIQUE INDEX idx_tag_name ON tags (name)", + "CREATE UNIQUE INDEX idx_view_history_url_device ON view_history (url, device)", + "CREATE INDEX idx_view_history_device ON view_history (device)" + }; + + private static final String[] SQL_V1_V2_UPGRADE = { + "ALTER TABLE view_history ADD COLUMN currency TEXT" + }; + + private static final String SQL_INSERT_SUBSCRIPTION = "REPLACE INTO subscriptions (channel_name, url) VALUES (?, ?)"; + private static final String SQL_DELETE_SUBSCRIPTION = "DELETE FROM subscriptions WHERE url = ?"; + private static final String SQL_GET_SUBSCRIPTIONS = "SELECT channel_name, url FROM subscriptions"; + + private static final String SQL_INSERT_URL_HISTORY = "REPLACE INTO url_history (value, url, type, timestamp) VALUES (?, ?, ?, ?)"; + private static final String SQL_CLEAR_URL_HISTORY = "DELETE FROM url_history"; + private static final String SQL_CLEAR_URL_HISTORY_BEFORE_TIME = "DELETE FROM url_history WHERE timestamp < ?"; + private static final String SQL_GET_RECENT_URL_HISTORY = "SELECT value, url, type FROM url_history ORDER BY timestamp DESC LIMIT 10"; + + private static final String SQL_INSERT_VIEW_HISTORY = + "REPLACE INTO view_history (url, claim_id, claim_name, cost, currency, title, publisher_claim_id, publisher_name, publisher_title, thumbnail_url, device, release_time, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + private static final String SQL_GET_VIEW_HISTORY = + "SELECT url, claim_id, claim_name, cost, currency, title, publisher_claim_id, publisher_name, publisher_title, thumbnail_url, device, release_time, timestamp " + + "FROM view_history WHERE '' = ? OR timestamp < ? ORDER BY timestamp DESC LIMIT %d"; + private static final String SQL_CLEAR_VIEW_HISTORY = "DELETE FROM view_history"; + private static final String SQL_CLEAR_VIEW_HISTORY_BY_DEVICE = "DELETE FROM view_history WHERE device = ?"; + private static final String SQL_CLEAR_VIEW_HISTORY_BEFORE_TIME = "DELETE FROM view_history WHERE timestamp < ?"; + private static final String SQL_CLEAR_VIEW_HISTORY_BY_DEVICE_BEFORE_TIME = "DELETE FROM view_history WHERE device = ? AND timestamp < ?"; + + private static final String SQL_INSERT_TAG = "REPLACE INTO tags (name, is_followed) VALUES (?, ?)"; + private static final String SQL_GET_KNOWN_TAGS = "SELECT name, is_followed FROM tags"; + private static final String SQL_UNFOLLOW_TAGS = "UPDATE tags SET is_followed = 0"; + private static final String SQL_GET_FOLLOWED_TAGS = "SELECT name FROM tags WHERE is_followed = 1"; + + + + public DatabaseHelper(Context context) { + super(context, String.format("%s/%s", context.getFilesDir().getAbsolutePath(), DATABASE_NAME), null, DATABASE_VERSION); + instance = this; + } + public static DatabaseHelper getInstance() { + return instance; + } + public void onCreate(SQLiteDatabase db) { + for (String sql : SQL_CREATE_TABLES) { + db.execSQL(sql); + } + for (String sql : SQL_CREATE_INDEXES) { + db.execSQL(sql); + } + } + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion < 2) { + for (String sql : SQL_V1_V2_UPGRADE) { + db.execSQL(sql); + } + } + } + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + } + + public static void createOrUpdateUrlHistoryItem(String text, String url, int type, SQLiteDatabase db) { + db.execSQL(SQL_INSERT_URL_HISTORY, new Object[] { + text, url, type, new SimpleDateFormat(Helper.ISO_DATE_FORMAT_PATTERN).format(new Date()) + }); + } + public static void clearUrlHistory(SQLiteDatabase db) { + db.execSQL(SQL_CLEAR_URL_HISTORY); + } + public static void clearUrlHistoryBefore(Date date, SQLiteDatabase db) { + db.execSQL(SQL_CLEAR_URL_HISTORY_BEFORE_TIME, new Object[] { new SimpleDateFormat(Helper.ISO_DATE_FORMAT_PATTERN).format(new Date()) }); + } + // History items are essentially url suggestions + public static List getRecentHistory(SQLiteDatabase db) { + List suggestions = new ArrayList<>(); + Cursor cursor = null; + try { + cursor = db.rawQuery(SQL_GET_RECENT_URL_HISTORY, null); + while (cursor.moveToNext()) { + UrlSuggestion suggestion = new UrlSuggestion(); + suggestion.setText(cursor.getString(0)); + suggestion.setUri(cursor.isNull(1) ? null : LbryUri.tryParse(cursor.getString(1))); + suggestion.setType(cursor.getInt(2)); + suggestion.setTitleUrlOnly(true); + suggestions.add(suggestion); + } + } finally { + Helper.closeCursor(cursor); + } + return suggestions; + } + + // View history items are stream claims + public static void createOrUpdateViewHistoryItem(ViewHistory viewHistory, SQLiteDatabase db) { + db.execSQL(SQL_INSERT_VIEW_HISTORY, new Object[] { + viewHistory.getUri().toString(), + viewHistory.getClaimId(), + viewHistory.getClaimName(), + viewHistory.getCost() != null ? viewHistory.getCost().doubleValue() : 0, + viewHistory.getCurrency(), + viewHistory.getTitle(), + viewHistory.getPublisherClaimId(), + viewHistory.getPublisherName(), + viewHistory.getPublisherTitle(), + viewHistory.getThumbnailUrl(), + viewHistory.getDevice(), + viewHistory.getReleaseTime(), + new SimpleDateFormat(Helper.ISO_DATE_FORMAT_PATTERN).format(new Date()) + }); + } + + public static List getViewHistory(String lastTimestamp, int pageLimit, SQLiteDatabase db) { + List history = new ArrayList<>(); + Cursor cursor = null; + try { + String arg = lastTimestamp == null ? "" : lastTimestamp; + cursor = db.rawQuery(String.format(SQL_GET_VIEW_HISTORY, pageLimit), new String[] { arg, arg }); + while (cursor.moveToNext()) { + ViewHistory item = new ViewHistory(); + int cursorIndex = 0; + item.setUri(LbryUri.tryParse(cursor.getString(cursorIndex++))); + item.setClaimId(cursor.getString(cursorIndex++)); + item.setClaimName(cursor.getString(cursorIndex++)); + item.setCost(new BigDecimal(cursor.getDouble(cursorIndex++))); + item.setCurrency(cursor.getString(cursorIndex++)); + item.setTitle(cursor.getString(cursorIndex++)); + item.setPublisherClaimId(cursor.getString(cursorIndex++)); + item.setPublisherName(cursor.getString(cursorIndex++)); + item.setPublisherTitle(cursor.getString(cursorIndex++)); + item.setThumbnailUrl(cursor.getString(cursorIndex++)); + item.setDevice(cursor.getString(cursorIndex++)); + item.setReleaseTime(cursor.getLong(cursorIndex++)); + try { + item.setTimestamp(new SimpleDateFormat(Helper.ISO_DATE_FORMAT_PATTERN).parse(cursor.getString(cursorIndex))); + } catch (ParseException ex) { + // invalid timestamp (which shouldn't happen). Skip this item + continue; + } + + history.add(item); + } + } finally { + Helper.closeCursor(cursor); + } + return history; + } + + public static void createOrUpdateTag(Tag tag, SQLiteDatabase db) { + db.execSQL(SQL_INSERT_TAG, new Object[] { tag.getLowercaseName(), tag.isFollowed() ? 1 : 0 }); + } + public static void setAllTagsUnfollowed(SQLiteDatabase db) { + db.execSQL(SQL_UNFOLLOW_TAGS); + } + public static List getTags(SQLiteDatabase db) { + List tags = new ArrayList<>(); + Cursor cursor = null; + try { + cursor = db.rawQuery(SQL_GET_KNOWN_TAGS, null); + while (cursor.moveToNext()) { + Tag tag = new Tag(); + tag.setName(cursor.getString(0)); + tag.setFollowed(cursor.getInt(1) == 1); + tags.add(tag); + } + } finally { + Helper.closeCursor(cursor); + } + return tags; + } + + public static void createOrUpdateSubscription(Subscription subscription, SQLiteDatabase db) { + db.execSQL(SQL_INSERT_SUBSCRIPTION, new Object[] { subscription.getChannelName(), subscription.getUrl() }); + } + public static void deleteSubscription(Subscription subscription, SQLiteDatabase db) { + db.execSQL(SQL_DELETE_SUBSCRIPTION, new Object[] { subscription.getUrl() }); + } + public static List getSubscriptions(SQLiteDatabase db) { + List subscriptions = new ArrayList<>(); + Cursor cursor = null; + try { + cursor = db.rawQuery(SQL_GET_SUBSCRIPTIONS, null); + while (cursor.moveToNext()) { + Subscription subscription = new Subscription(); + subscription.setChannelName(cursor.getString(0)); + subscription.setUrl(cursor.getString(1)); + subscriptions.add(subscription); + } + } finally { + Helper.closeCursor(cursor); + } + return subscriptions; + } + +} diff --git a/app/src/main/java/io/lbry/browser/dialog/ContentFromDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/ContentFromDialogFragment.java new file mode 100644 index 00000000..d81102b7 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/dialog/ContentFromDialogFragment.java @@ -0,0 +1,112 @@ +package io.lbry.browser.dialog; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import io.lbry.browser.R; +import lombok.Setter; + +public class ContentFromDialogFragment extends BottomSheetDialogFragment { + public static final String TAG = "ContentFromDialog"; + public static final int ITEM_FROM_PAST_24_HOURS = 1; + public static final int ITEM_FROM_PAST_WEEK = 2; + public static final int ITEM_FROM_PAST_MONTH = 3; + public static final int ITEM_FROM_PAST_YEAR = 4; + public static final int ITEM_FROM_ALL_TIME = 5; + + @Setter + private ContentFromListener contentFromListener; + private int currentFromItem; + + public static ContentFromDialogFragment newInstance() { + return new ContentFromDialogFragment(); + } + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_content_from, container,false); + + ContentFromItemClickListener clickListener = new ContentFromItemClickListener(this, contentFromListener); + view.findViewById(R.id.content_from_past_24_hours_item).setOnClickListener(clickListener); + view.findViewById(R.id.content_from_past_week_item).setOnClickListener(clickListener); + view.findViewById(R.id.content_from_past_month_item).setOnClickListener(clickListener); + view.findViewById(R.id.content_from_past_year_item).setOnClickListener(clickListener); + view.findViewById(R.id.content_from_all_time_item).setOnClickListener(clickListener); + checkSelectedFromItem(currentFromItem, view); + + return view; + } + + public static void checkSelectedFromItem(int fromItem, View parent) { + int checkViewId = -1; + switch (fromItem) { + case ITEM_FROM_PAST_24_HOURS: checkViewId = R.id.content_from_past_24_hours_item_selected; break; + case ITEM_FROM_PAST_WEEK: checkViewId = R.id.content_from_past_week_item_selected; break; + case ITEM_FROM_PAST_MONTH: checkViewId = R.id.content_from_past_month_item_selected; break; + case ITEM_FROM_PAST_YEAR: checkViewId = R.id.content_from_past_year_item_selected; break; + case ITEM_FROM_ALL_TIME: checkViewId = R.id.content_from_all_time_item_selected; break; + } + if (parent != null && checkViewId > -1) { + parent.findViewById(checkViewId).setVisibility(View.VISIBLE); + } + } + + public void setCurrentFromItem(int fromItem) { + this.currentFromItem = fromItem; + } + + private static class ContentFromItemClickListener implements View.OnClickListener { + + private final int[] checkViewIds = { + R.id.content_from_past_24_hours_item, + R.id.content_from_past_week_item, + R.id.content_from_past_month_item, + R.id.content_from_past_year_item, + R.id.content_from_all_time_item + }; + private BottomSheetDialogFragment dialog; + private ContentFromListener listener; + + public ContentFromItemClickListener(BottomSheetDialogFragment dialog, ContentFromListener listener) { + this.dialog = dialog; + this.listener = listener; + } + + public void onClick(View view) { + int currentFromItem = -1; + + if (dialog != null) { + View dialogView = dialog.getView(); + if (dialogView != null) { + for (int id : checkViewIds) { + dialogView.findViewById(id).setVisibility(View.GONE); + } + } + } + + switch (view.getId()) { + case R.id.content_from_past_24_hours_item: currentFromItem = ITEM_FROM_PAST_24_HOURS; break; + case R.id.content_from_past_week_item: currentFromItem = ITEM_FROM_PAST_WEEK; break; + case R.id.content_from_past_month_item: currentFromItem = ITEM_FROM_PAST_MONTH; break; + case R.id.content_from_past_year_item: currentFromItem = ITEM_FROM_PAST_YEAR; break; + case R.id.content_from_all_time_item: currentFromItem = ITEM_FROM_ALL_TIME; break; + } + + checkSelectedFromItem(currentFromItem, view); + if (listener != null) { + listener.onContentFromItemSelected(currentFromItem); + } + + if (dialog != null) { + dialog.dismiss(); + } + } + } + + public interface ContentFromListener { + void onContentFromItemSelected(int contentFromItem); + } +} diff --git a/app/src/main/java/io/lbry/browser/dialog/ContentScopeDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/ContentScopeDialogFragment.java new file mode 100644 index 00000000..4db24f8d --- /dev/null +++ b/app/src/main/java/io/lbry/browser/dialog/ContentScopeDialogFragment.java @@ -0,0 +1,96 @@ +package io.lbry.browser.dialog; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import io.lbry.browser.R; +import lombok.Setter; + +public class ContentScopeDialogFragment extends BottomSheetDialogFragment { + public static final String TAG = "ContentScopeDialog"; + public static final int ITEM_EVERYONE = 1; + public static final int ITEM_TAGS = 2; + + @Setter + private ContentScopeListener contentScopeListener; + private int currentScopeItem; + + public static ContentScopeDialogFragment newInstance() { + return new ContentScopeDialogFragment(); + } + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_content_scope, container,false); + + ContentScopeItemClickListener clickListener = new ContentScopeItemClickListener(this, contentScopeListener); + view.findViewById(R.id.content_scope_everyone_item).setOnClickListener(clickListener); + view.findViewById(R.id.content_scope_tags_item).setOnClickListener(clickListener); + checkSelectedScopeItem(currentScopeItem, view); + + return view; + } + + public static void checkSelectedScopeItem(int scope, View parent) { + int checkViewId = -1; + switch (scope) { + case ITEM_EVERYONE: checkViewId = R.id.content_scope_everyone_item_selected; break; + case ITEM_TAGS: checkViewId = R.id.content_scope_tags_item_selected; break; + } + if (parent != null && checkViewId > -1) { + parent.findViewById(checkViewId).setVisibility(View.VISIBLE); + } + } + + public void setCurrentScopeItem(int scopeItem) { + this.currentScopeItem = scopeItem; + } + + private static class ContentScopeItemClickListener implements View.OnClickListener { + + private final int[] checkViewIds = { + R.id.content_scope_everyone_item_selected, R.id.content_scope_tags_item_selected + }; + private BottomSheetDialogFragment dialog; + private ContentScopeListener listener; + + public ContentScopeItemClickListener(BottomSheetDialogFragment dialog, ContentScopeListener listener) { + this.dialog = dialog; + this.listener = listener; + } + + public void onClick(View view) { + int scopeItem = -1; + + if (dialog != null) { + View dialogView = dialog.getView(); + if (dialogView != null) { + for (int id : checkViewIds) { + dialogView.findViewById(id).setVisibility(View.GONE); + } + } + } + + switch (view.getId()) { + case R.id.content_scope_everyone_item: scopeItem = ITEM_EVERYONE; break; + case R.id.content_scope_tags_item: scopeItem = ITEM_TAGS; break; + } + + checkSelectedScopeItem(scopeItem, view); + if (listener != null) { + listener.onContentScopeItemSelected(scopeItem); + } + + if (dialog != null) { + dialog.dismiss(); + } + } + } + + public interface ContentScopeListener { + void onContentScopeItemSelected(int scopeItem); + } +} diff --git a/app/src/main/java/io/lbry/browser/dialog/ContentSortDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/ContentSortDialogFragment.java new file mode 100644 index 00000000..06ac5e57 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/dialog/ContentSortDialogFragment.java @@ -0,0 +1,100 @@ +package io.lbry.browser.dialog; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import io.lbry.browser.R; +import lombok.Setter; + +public class ContentSortDialogFragment extends BottomSheetDialogFragment { + public static final String TAG = "ContentSortDialog"; + public static final int ITEM_SORT_BY_TRENDING = 1; + public static final int ITEM_SORT_BY_NEW = 2; + public static final int ITEM_SORT_BY_TOP = 3; + + @Setter + private SortByListener sortByListener; + private int currentSortByItem; + + public static ContentSortDialogFragment newInstance() { + return new ContentSortDialogFragment(); + } + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_content_sort, container,false); + + SortByItemClickListener clickListener = new SortByItemClickListener(this, sortByListener); + view.findViewById(R.id.sort_by_trending_item).setOnClickListener(clickListener); + view.findViewById(R.id.sort_by_new_item).setOnClickListener(clickListener); + view.findViewById(R.id.sort_by_top_item).setOnClickListener(clickListener); + checkSelectedSortByItem(currentSortByItem, view); + + return view; + } + + public static void checkSelectedSortByItem(int sortByItem, View parent) { + int checkViewId = -1; + switch (sortByItem) { + case ITEM_SORT_BY_TRENDING: checkViewId = R.id.sort_by_trending_item_selected; break; + case ITEM_SORT_BY_NEW: checkViewId = R.id.sort_by_new_item_selected; break; + case ITEM_SORT_BY_TOP: checkViewId = R.id.sort_by_top_item_selected; break; + } + if (parent != null && checkViewId > -1) { + parent.findViewById(checkViewId).setVisibility(View.VISIBLE); + } + } + + public void setCurrentSortByItem(int sortByItem) { + this.currentSortByItem = sortByItem; + } + + private static class SortByItemClickListener implements View.OnClickListener { + + private final int[] checkViewIds = { + R.id.sort_by_trending_item_selected, R.id.sort_by_new_item_selected, R.id.sort_by_top_item_selected + }; + private BottomSheetDialogFragment dialog; + private SortByListener listener; + + public SortByItemClickListener(BottomSheetDialogFragment dialog, SortByListener listener) { + this.dialog = dialog; + this.listener = listener; + } + + public void onClick(View view) { + int selectedSortByItem = -1; + + if (dialog != null) { + View dialogView = dialog.getView(); + if (dialogView != null) { + for (int id : checkViewIds) { + dialogView.findViewById(id).setVisibility(View.GONE); + } + } + } + + switch (view.getId()) { + case R.id.sort_by_trending_item: selectedSortByItem = ITEM_SORT_BY_TRENDING; break; + case R.id.sort_by_new_item: selectedSortByItem = ITEM_SORT_BY_NEW; break; + case R.id.sort_by_top_item: selectedSortByItem = ITEM_SORT_BY_TOP; break; + } + + checkSelectedSortByItem(selectedSortByItem, view); + if (listener != null) { + listener.onSortByItemSelected(selectedSortByItem); + } + + if (dialog != null) { + dialog.dismiss(); + } + } + } + + public interface SortByListener { + void onSortByItemSelected(int sortBy); + } +} diff --git a/app/src/main/java/io/lbry/browser/dialog/CustomizeTagsDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/CustomizeTagsDialogFragment.java new file mode 100644 index 00000000..1a328108 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/dialog/CustomizeTagsDialogFragment.java @@ -0,0 +1,186 @@ +package io.lbry.browser.dialog; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.flexbox.FlexboxLayoutManager; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.TextInputEditText; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.R; +import io.lbry.browser.adapter.TagListAdapter; +import io.lbry.browser.listener.TagListener; +import io.lbry.browser.model.Tag; +import io.lbry.browser.tasks.UpdateSuggestedTagsTask; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import lombok.Setter; + +public class CustomizeTagsDialogFragment extends BottomSheetDialogFragment { + public static final String TAG = "CustomizeTagsDialog"; + private static final int SUGGESTED_LIMIT = 8; + private String currentFilter; + + private RecyclerView followedTagsList; + private RecyclerView suggestedTagsList; + private TagListAdapter followedTagsAdapter; + private TagListAdapter suggestedTagsAdapter; + private View noTagsView; + private View noResultsView; + @Setter + private TagListener listener; + + private void checkNoTags() { + Helper.setViewVisibility(noTagsView, followedTagsAdapter == null || followedTagsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + private void checkNoResults() { + Helper.setViewVisibility(noResultsView, suggestedTagsAdapter == null || suggestedTagsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + public void addTag(Tag tag) { + if (followedTagsAdapter.getTags().contains(tag)) { + Snackbar.make(getView(), getString(R.string.tag_already_added, tag.getName()), Snackbar.LENGTH_LONG).show(); + return; + } + + tag.setFollowed(true); + followedTagsAdapter.addTag(tag); + if (suggestedTagsAdapter != null) { + suggestedTagsAdapter.removeTag(tag); + } + updateKnownTags(currentFilter, SUGGESTED_LIMIT, false); + if (listener != null) { + listener.onTagAdded(tag); + } + checkNoTags(); + checkNoResults(); + } + public void removeTag(Tag tag) { + tag.setFollowed(false); + followedTagsAdapter.removeTag(tag); + updateKnownTags(currentFilter, SUGGESTED_LIMIT, false); + if (listener != null) { + listener.onTagRemoved(tag); + } + checkNoTags(); + checkNoResults(); + } + + public void setFilter(String filter) { + currentFilter = filter; + updateKnownTags(currentFilter, SUGGESTED_LIMIT, true); + } + + public static CustomizeTagsDialogFragment newInstance() { + return new CustomizeTagsDialogFragment(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_customize_tags, container, false); + + noResultsView = view.findViewById(R.id.customize_no_tag_results); + noTagsView = view.findViewById(R.id.customize_no_followed_tags); + + followedTagsAdapter = new TagListAdapter(Lbry.followedTags, getContext()); + followedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_REMOVE); + followedTagsAdapter.setClickListener(customizeTagClickListener); + suggestedTagsAdapter = new TagListAdapter(new ArrayList<>(), getContext()); + suggestedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD); + suggestedTagsAdapter.setClickListener(customizeTagClickListener); + + FlexboxLayoutManager flm1 = new FlexboxLayoutManager(getContext()); + followedTagsList = view.findViewById(R.id.customize_tags_followed_list); + followedTagsList.setLayoutManager(flm1); + followedTagsList.setAdapter(followedTagsAdapter); + + FlexboxLayoutManager flm2 = new FlexboxLayoutManager(getContext()); + suggestedTagsList = view.findViewById(R.id.customize_tags_suggested_list); + suggestedTagsList.setLayoutManager(flm2); + suggestedTagsList.setAdapter(suggestedTagsAdapter); + + TextInputEditText filterInput = view.findViewById(R.id.customize_tag_filter_input); + filterInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + String value = Helper.getValue(charSequence); + setFilter(value); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + + + MaterialButton doneButton = view.findViewById(R.id.customize_done_button); + doneButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + checkNoTags(); + return view; + } + + private TagListAdapter.TagClickListener customizeTagClickListener = new TagListAdapter.TagClickListener() { + @Override + public void onTagClicked(Tag tag, int customizeMode) { + if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_ADD) { + addTag(tag); + } else if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_REMOVE) { + removeTag(tag); + } + } + }; + + public void onResume() { + super.onResume(); + updateKnownTags(null, SUGGESTED_LIMIT, true); + } + + private void updateKnownTags(String filter, int limit, boolean clearPrevious) { + UpdateSuggestedTagsTask task = new UpdateSuggestedTagsTask( + filter, + SUGGESTED_LIMIT, + followedTagsAdapter, + suggestedTagsAdapter, + clearPrevious, + false, new UpdateSuggestedTagsTask.KnownTagsHandler() { + @Override + public void onSuccess(List tags) { + if (suggestedTagsAdapter == null) { + suggestedTagsAdapter = new TagListAdapter(tags, getContext()); + suggestedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD); + suggestedTagsAdapter.setClickListener(customizeTagClickListener); + if (suggestedTagsList != null) { + suggestedTagsList.setAdapter(suggestedTagsAdapter); + } + } else { + suggestedTagsAdapter.setTags(tags); + } + checkNoResults(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } +} diff --git a/app/src/main/java/io/lbry/browser/dialog/DiscoverDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/DiscoverDialogFragment.java new file mode 100644 index 00000000..927ebda1 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/dialog/DiscoverDialogFragment.java @@ -0,0 +1,107 @@ +package io.lbry.browser.dialog; + +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.button.MaterialButton; + +import io.lbry.browser.R; +import io.lbry.browser.adapter.SuggestedChannelGridAdapter; +import lombok.Getter; +import lombok.Setter; + +public class DiscoverDialogFragment extends BottomSheetDialogFragment { + public static final String TAG = "DiscoverDialog"; + + @Getter + private SuggestedChannelGridAdapter adapter; + @Setter + private DiscoverDialogListener dialogActionsListener; + + public static DiscoverDialogFragment newInstance() { + return new DiscoverDialogFragment(); + } + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_discover, container, false); + + RecyclerView grid = view.findViewById(R.id.discover_channel_grid); + GridLayoutManager glm = new GridLayoutManager(getContext(), 3); + grid.setLayoutManager(glm); + grid.setAdapter(adapter); + grid.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + GridLayoutManager lm = (GridLayoutManager) recyclerView.getLayoutManager(); + if (lm != null) { + int visibleItemCount = lm.getChildCount(); + int totalItemCount = lm.getItemCount(); + int pastVisibleItems = lm.findFirstVisibleItemPosition(); + if (pastVisibleItems + visibleItemCount >= totalItemCount) { + if (dialogActionsListener != null) { + dialogActionsListener.onScrollEndReached(); + } + } + } + } + }); + + MaterialButton doneButton = view.findViewById(R.id.discover_done_button); + doneButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + return view; + } + public void setAdapter(SuggestedChannelGridAdapter adapter) { + this.adapter = adapter; + if (getView() != null) { + ((RecyclerView) getView().findViewById(R.id.discover_channel_grid)).setAdapter(adapter); + } + } + public void setLoading(boolean loading) { + if (getView() != null) { + getView().findViewById(R.id.discover_loading).setVisibility(loading ? View.VISIBLE : View.GONE); + } + } + + @Override + public void onResume() { + super.onResume(); + if (dialogActionsListener != null) { + dialogActionsListener.onResume(); + } + } + @Override + public void onCancel(DialogInterface dialog) { + super.onDismiss(dialog); + if (dialogActionsListener != null) { + dialogActionsListener.onCancel(); + } + } + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + if (dialogActionsListener != null) { + dialogActionsListener.onCancel(); + } + } + + public interface DiscoverDialogListener { + void onResume(); + void onCancel(); + void onScrollEndReached(); + } +} diff --git a/app/src/main/java/io/lbry/browser/dialog/RepostClaimDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/RepostClaimDialogFragment.java new file mode 100644 index 00000000..f34099ce --- /dev/null +++ b/app/src/main/java/io/lbry/browser/dialog/RepostClaimDialogFragment.java @@ -0,0 +1,291 @@ +package io.lbry.browser.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.appcompat.widget.AppCompatSpinner; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.TextInputEditText; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.InlineChannelSpinnerAdapter; +import io.lbry.browser.listener.WalletBalanceListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimListTask; +import io.lbry.browser.tasks.claim.ClaimResultHandler; +import io.lbry.browser.tasks.claim.StreamRepostTask; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryUri; +import lombok.Setter; + +public class RepostClaimDialogFragment extends BottomSheetDialogFragment implements WalletBalanceListener { + public static final String TAG = "RepostClaimDialog"; + + private MaterialButton buttonRepost; + private View linkCancel; + private TextInputEditText inputDeposit; + private View inlineBalanceContainer; + private TextView inlineBalanceValue; + private ProgressBar repostProgress; + private TextView textTitle; + + private AppCompatSpinner channelSpinner; + private InlineChannelSpinnerAdapter channelSpinnerAdapter; + private TextView textNamePrefix; + private EditText inputName; + private TextView linkToggleAdvanced; + private View advancedContainer; + + @Setter + private RepostClaimListener listener; + @Setter + private Claim claim; + + public static RepostClaimDialogFragment newInstance() { + return new RepostClaimDialogFragment(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_repost_claim, container, false); + + buttonRepost = view.findViewById(R.id.repost_button); + linkCancel = view.findViewById(R.id.repost_cancel_link); + inputDeposit = view.findViewById(R.id.repost_input_deposit); + inlineBalanceContainer = view.findViewById(R.id.repost_inline_balance_container); + inlineBalanceValue = view.findViewById(R.id.repost_inline_balance_value); + repostProgress = view.findViewById(R.id.repost_progress); + textTitle = view.findViewById(R.id.repost_title); + + channelSpinner = view.findViewById(R.id.repost_channel_spinner); + textNamePrefix = view.findViewById(R.id.repost_name_prefix); + inputName = view.findViewById(R.id.repost_name_input); + linkToggleAdvanced = view.findViewById(R.id.repost_toggle_advanced); + advancedContainer = view.findViewById(R.id.repost_advanced_container); + + textTitle.setText(getString(R.string.repost_title, claim.getTitle())); + inputName.setText(claim.getName()); + inputDeposit.setText(R.string.min_deposit); + channelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, long l) { + Object item = adapterView.getItemAtPosition(position); + if (item instanceof Claim) { + Claim claim = (Claim) item; + textNamePrefix.setText(String.format("%s%s/", LbryUri.PROTO_DEFAULT, claim.getName())); + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + inputDeposit.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + inputDeposit.setHint(hasFocus ? getString(R.string.zero) : ""); + inlineBalanceContainer.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); + } + }); + + linkCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + linkToggleAdvanced.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (advancedContainer.getVisibility() != View.VISIBLE) { + advancedContainer.setVisibility(View.VISIBLE); + linkToggleAdvanced.setText(R.string.hide_advanced); + } else { + advancedContainer.setVisibility(View.GONE); + linkToggleAdvanced.setText(R.string.show_advanced); + } + } + }); + + buttonRepost.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + validateAndRepostClaim(); + } + }); + + onWalletBalanceUpdated(Lbry.walletBalance); + + return view; + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).addWalletBalanceListener(this); + } + fetchChannels(); + } + + public void onPause() { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).removeWalletBalanceListener(this); + } + inputDeposit.clearFocus(); + super.onPause(); + } + + + private void fetchChannels() { + if (Lbry.ownChannels == null || Lbry.ownChannels.size() == 0) { + startLoading(); + ClaimListTask task = new ClaimListTask(Claim.TYPE_CHANNEL, repostProgress, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + Lbry.ownChannels = new ArrayList<>(claims); + loadChannels(claims); + finishLoading(); + } + + @Override + public void onError(Exception error) { + // could not fetch channels + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).showError(error.getMessage()); + } + dismiss(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + loadChannels(Lbry.ownChannels); + } + } + + private void loadChannels(List channels) { + if (channelSpinnerAdapter == null) { + Context context = getContext(); + channelSpinnerAdapter = new InlineChannelSpinnerAdapter(context, R.layout.spinner_item_channel, channels); + channelSpinnerAdapter.notifyDataSetChanged(); + } else { + channelSpinnerAdapter.clear(); + channelSpinnerAdapter.addAll(channels); + channelSpinnerAdapter.notifyDataSetChanged(); + } + if (channelSpinner != null) { + channelSpinner.setAdapter(channelSpinnerAdapter); + } + } + + @Override + public void onWalletBalanceUpdated(WalletBalance walletBalance) { + if (walletBalance != null && inlineBalanceValue != null) { + inlineBalanceValue.setText(Helper.shortCurrencyFormat(walletBalance.getAvailable().doubleValue())); + } + } + + private void validateAndRepostClaim() { + String name = Helper.getValue(inputName.getText()); + if (Helper.isNullOrEmpty(name) || !LbryUri.isNameValid(name)) { + showError(getString(R.string.repost_name_invalid_characters)); + return; + } + + String depositString = Helper.getValue(inputDeposit.getText()); + if (Helper.isNullOrEmpty(depositString)) { + showError(getString(R.string.invalid_amount)); + return; + } + + BigDecimal bid = new BigDecimal(depositString); + if (bid.doubleValue() > Lbry.walletBalance.getAvailable().doubleValue()) { + showError(getString(R.string.insufficient_balance)); + return; + } + + Claim channel = (Claim) channelSpinner.getSelectedItem(); + StreamRepostTask task = new StreamRepostTask(name, bid, claim.getClaimId(), channel.getClaimId(), repostProgress, new ClaimResultHandler() { + @Override + public void beforeStart() { + startLoading(); + } + + @Override + public void onSuccess(Claim claimResult) { + if (listener != null) { + listener.onClaimReposted(claimResult); + } + finishLoading(); + dismiss(); + } + + @Override + public void onError(Exception error) { + showError(error.getMessage()); + finishLoading(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void showError(String message) { + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED). + setTextColor(Color.WHITE). + show(); + } + + private void startLoading() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.setCanceledOnTouchOutside(false); + } + linkCancel.setEnabled(false); + buttonRepost.setEnabled(false); + inputName.setEnabled(false); + channelSpinner.setEnabled(false); + linkToggleAdvanced.setVisibility(View.INVISIBLE); + } + private void finishLoading() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.setCanceledOnTouchOutside(true); + } + linkCancel.setEnabled(true); + buttonRepost.setEnabled(true); + inputName.setEnabled(true); + channelSpinner.setEnabled(true); + linkToggleAdvanced.setVisibility(View.VISIBLE); + } + + public interface RepostClaimListener { + void onClaimReposted(Claim claim); + } +} diff --git a/app/src/main/java/io/lbry/browser/dialog/SendTipDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/SendTipDialogFragment.java new file mode 100644 index 00000000..6be2d917 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/dialog/SendTipDialogFragment.java @@ -0,0 +1,196 @@ +package io.lbry.browser.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.core.content.ContextCompat; +import androidx.core.text.HtmlCompat; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.TextInputEditText; + +import java.math.BigDecimal; +import java.text.DecimalFormat; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.listener.WalletBalanceListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.tasks.wallet.SupportCreateTask; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import lombok.Setter; + +public class SendTipDialogFragment extends BottomSheetDialogFragment implements WalletBalanceListener { + public static final String TAG = "SendTipDialog"; + + private MaterialButton sendButton; + private View cancelLink; + private TextInputEditText inputAmount; + private View inlineBalanceContainer; + private TextView inlineBalanceValue; + private ProgressBar sendProgress; + + @Setter + private SendTipListener listener; + @Setter + private Claim claim; + + public static SendTipDialogFragment newInstance() { + return new SendTipDialogFragment(); + } + + private void disableControls() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.setCanceledOnTouchOutside(false); + } + sendButton.setEnabled(false); + cancelLink.setEnabled(false); + } + private void enableControls() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.setCanceledOnTouchOutside(true); + } + sendButton.setEnabled(true); + cancelLink.setEnabled(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_send_tip, container, false); + + inputAmount = view.findViewById(R.id.tip_input_amount); + inlineBalanceContainer = view.findViewById(R.id.tip_inline_balance_container); + inlineBalanceValue = view.findViewById(R.id.tip_inline_balance_value); + sendProgress = view.findViewById(R.id.tip_send_progress); + cancelLink = view.findViewById(R.id.tip_cancel_link); + sendButton = view.findViewById(R.id.tip_send); + + inputAmount.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + inputAmount.setHint(hasFocus ? getString(R.string.zero) : ""); + inlineBalanceContainer.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); + } + }); + + TextView infoText = view.findViewById(R.id.tip_info); + infoText.setMovementMethod(LinkMovementMethod.getInstance()); + infoText.setText(HtmlCompat.fromHtml( + Claim.TYPE_CHANNEL.equalsIgnoreCase(claim.getValueType()) ? + getString(R.string.send_tip_info_channel, claim.getTitleOrName()) : + getString(R.string.send_tip_info_content, claim.getTitleOrName()), + HtmlCompat.FROM_HTML_MODE_LEGACY)); + + sendButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String amountString = Helper.getValue(inputAmount.getText()); + if (Helper.isNullOrEmpty(amountString)) { + showError(getString(R.string.invalid_amount)); + return; + } + + BigDecimal amount = new BigDecimal(amountString); + if (amount.doubleValue() > Lbry.walletBalance.getAvailable().doubleValue()) { + showError(getString(R.string.insufficient_balance)); + return; + } + + SupportCreateTask task = new SupportCreateTask(claim.getClaimId(), amount, true, sendProgress, new GenericTaskHandler() { + @Override + public void beforeStart() { + disableControls(); + } + + @Override + public void onSuccess() { + enableControls(); + if (listener != null) { + listener.onTipSent(amount); + } + + dismiss(); + } + + @Override + public void onError(Exception error) { + showError(error.getMessage()); + enableControls(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + cancelLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + String channel = null; + if (Claim.TYPE_CHANNEL.equalsIgnoreCase(claim.getValueType())) { + channel = claim.getTitleOrName(); + } else if (claim.getSigningChannel() != null) { + channel = claim.getPublisherTitle(); + } + ((TextView) view.findViewById(R.id.tip_send_title)).setText( + Helper.isNullOrEmpty(channel) ? getString(R.string.send_a_tip) : getString(R.string.send_a_tip_to, channel) + ); + + onWalletBalanceUpdated(Lbry.walletBalance); + + return view; + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).addWalletBalanceListener(this); + } + } + + public void onPause() { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).removeWalletBalanceListener(this); + } + super.onPause(); + } + + @Override + public void onWalletBalanceUpdated(WalletBalance walletBalance) { + if (walletBalance != null && inlineBalanceValue != null) { + inlineBalanceValue.setText(Helper.shortCurrencyFormat(walletBalance.getAvailable().doubleValue())); + } + } + + private void showError(String message) { + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED). + setTextColor(Color.WHITE). + show(); + } + + public interface SendTipListener { + void onTipSent(BigDecimal amount); + } +} diff --git a/app/src/main/java/io/lbry/browser/exceptions/ApiCallException.java b/app/src/main/java/io/lbry/browser/exceptions/ApiCallException.java new file mode 100644 index 00000000..652576c6 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/exceptions/ApiCallException.java @@ -0,0 +1,13 @@ +package io.lbry.browser.exceptions; + +public class ApiCallException extends Exception { + public ApiCallException() { + super(); + } + public ApiCallException(String message) { + super(message); + } + public ApiCallException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/app/src/main/java/io/lbry/browser/exceptions/LbryRequestException.java b/app/src/main/java/io/lbry/browser/exceptions/LbryRequestException.java new file mode 100644 index 00000000..a71a09e6 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/exceptions/LbryRequestException.java @@ -0,0 +1,13 @@ +package io.lbry.browser.exceptions; + +public class LbryRequestException extends Exception { + public LbryRequestException() { + super(); + } + public LbryRequestException(String message) { + super(message); + } + public LbryRequestException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/app/src/main/java/io/lbry/browser/exceptions/LbryResponseException.java b/app/src/main/java/io/lbry/browser/exceptions/LbryResponseException.java new file mode 100644 index 00000000..63a67e79 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/exceptions/LbryResponseException.java @@ -0,0 +1,13 @@ +package io.lbry.browser.exceptions; + +public class LbryResponseException extends Exception { + public LbryResponseException() { + super(); + } + public LbryResponseException(String message) { + super(message); + } + public LbryResponseException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/app/src/main/java/io/lbry/browser/exceptions/LbryUriException.java b/app/src/main/java/io/lbry/browser/exceptions/LbryUriException.java new file mode 100644 index 00000000..34fbcb22 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/exceptions/LbryUriException.java @@ -0,0 +1,13 @@ +package io.lbry.browser.exceptions; + +public class LbryUriException extends Exception { + public LbryUriException() { + super(); + } + public LbryUriException(String message) { + super(message); + } + public LbryUriException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/app/src/main/java/io/lbry/browser/exceptions/LbryioRequestException.java b/app/src/main/java/io/lbry/browser/exceptions/LbryioRequestException.java new file mode 100644 index 00000000..bb88a0e7 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/exceptions/LbryioRequestException.java @@ -0,0 +1,13 @@ +package io.lbry.browser.exceptions; + +public class LbryioRequestException extends Exception { + public LbryioRequestException() { + super(); + } + public LbryioRequestException(String message) { + super(message); + } + public LbryioRequestException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/app/src/main/java/io/lbry/browser/exceptions/LbryioResponseException.java b/app/src/main/java/io/lbry/browser/exceptions/LbryioResponseException.java new file mode 100644 index 00000000..a2a2b7b4 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/exceptions/LbryioResponseException.java @@ -0,0 +1,25 @@ +package io.lbry.browser.exceptions; + +import lombok.Getter; + +public class LbryioResponseException extends Exception { + @Getter + private int statusCode; + public LbryioResponseException() { + super(); + } + public LbryioResponseException(String message) { + super(message); + } + public LbryioResponseException(String message, Throwable cause) { + super(message, cause); + } + public LbryioResponseException(String message, int statusCode) { + super(message); + this.statusCode = statusCode; + } + public LbryioResponseException(String message, Throwable cause, int statusCode) { + super(message, cause); + this.statusCode = statusCode; + } +} diff --git a/app/src/main/java/io/lbry/browser/exceptions/WalletException.java b/app/src/main/java/io/lbry/browser/exceptions/WalletException.java new file mode 100644 index 00000000..b6dcbc40 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/exceptions/WalletException.java @@ -0,0 +1,13 @@ +package io.lbry.browser.exceptions; + +public class WalletException extends Exception { + public WalletException() { + super(); + } + public WalletException(String message) { + super(message); + } + public WalletException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/app/src/main/java/io/lbry/browser/listener/CameraPermissionListener.java b/app/src/main/java/io/lbry/browser/listener/CameraPermissionListener.java new file mode 100644 index 00000000..ca228c18 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/CameraPermissionListener.java @@ -0,0 +1,8 @@ +package io.lbry.browser.listener; + +public interface CameraPermissionListener { + void onCameraPermissionGranted(); + void onCameraPermissionRefused(); + void onRecordAudioPermissionGranted(); + void onRecordAudioPermissionRefused(); +} diff --git a/app/src/main/java/io/lbry/browser/listener/ChannelItemSelectionListener.java b/app/src/main/java/io/lbry/browser/listener/ChannelItemSelectionListener.java new file mode 100644 index 00000000..39e06c14 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/ChannelItemSelectionListener.java @@ -0,0 +1,9 @@ +package io.lbry.browser.listener; + +import io.lbry.browser.model.Claim; + +public interface ChannelItemSelectionListener { + void onChannelItemSelected(Claim claim); + void onChannelItemDeselected(Claim claim); + void onChannelSelectionCleared(); +} diff --git a/app/src/main/java/io/lbry/browser/listener/DownloadActionListener.java b/app/src/main/java/io/lbry/browser/listener/DownloadActionListener.java new file mode 100644 index 00000000..c93f950e --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/DownloadActionListener.java @@ -0,0 +1,5 @@ +package io.lbry.browser.listener; + +public interface DownloadActionListener { + void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress); +} diff --git a/app/src/main/java/io/lbry/browser/listener/FetchChannelsListener.java b/app/src/main/java/io/lbry/browser/listener/FetchChannelsListener.java new file mode 100644 index 00000000..b2f41e21 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/FetchChannelsListener.java @@ -0,0 +1,9 @@ +package io.lbry.browser.listener; + +import java.util.List; + +import io.lbry.browser.model.Claim; + +public interface FetchChannelsListener { + void onChannelsFetched(List channels); +} diff --git a/app/src/main/java/io/lbry/browser/listener/FetchClaimsListener.java b/app/src/main/java/io/lbry/browser/listener/FetchClaimsListener.java new file mode 100644 index 00000000..8a0bbda5 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/FetchClaimsListener.java @@ -0,0 +1,9 @@ +package io.lbry.browser.listener; + +import java.util.List; + +import io.lbry.browser.model.Claim; + +public interface FetchClaimsListener { + void onClaimsFetched(List claims); +} diff --git a/app/src/main/java/io/lbry/browser/listener/FilePickerListener.java b/app/src/main/java/io/lbry/browser/listener/FilePickerListener.java new file mode 100644 index 00000000..e4dc8128 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/FilePickerListener.java @@ -0,0 +1,6 @@ +package io.lbry.browser.listener; + +public interface FilePickerListener { + void onFilePicked(String filePath); + void onFilePickerCancelled(); +} diff --git a/app/src/main/java/io/lbry/browser/listener/SdkStatusListener.java b/app/src/main/java/io/lbry/browser/listener/SdkStatusListener.java new file mode 100644 index 00000000..866ff382 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/SdkStatusListener.java @@ -0,0 +1,5 @@ +package io.lbry.browser.listener; + +public interface SdkStatusListener { + void onSdkReady(); +} diff --git a/app/src/main/java/io/lbry/browser/listener/SelectionModeListener.java b/app/src/main/java/io/lbry/browser/listener/SelectionModeListener.java new file mode 100644 index 00000000..01fa043d --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/SelectionModeListener.java @@ -0,0 +1,7 @@ +package io.lbry.browser.listener; + +public interface SelectionModeListener { + void onEnterSelectionMode(); + void onExitSelectionMode(); + void onItemSelectionToggled(); +} diff --git a/app/src/main/java/io/lbry/browser/listener/SignInListener.java b/app/src/main/java/io/lbry/browser/listener/SignInListener.java new file mode 100644 index 00000000..866e2774 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/SignInListener.java @@ -0,0 +1,10 @@ +package io.lbry.browser.listener; + +public interface SignInListener { + void onEmailAdded(String email); + void onEmailEdit(); + void onEmailVerified(); + void onPhoneAdded(String countryCode, String phoneNumber); + void onPhoneVerified(); + void onManualVerifyContinue(); +} diff --git a/app/src/main/java/io/lbry/browser/listener/StoragePermissionListener.java b/app/src/main/java/io/lbry/browser/listener/StoragePermissionListener.java new file mode 100644 index 00000000..44bcd3dc --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/StoragePermissionListener.java @@ -0,0 +1,6 @@ +package io.lbry.browser.listener; + +public interface StoragePermissionListener { + void onStoragePermissionGranted(); + void onStoragePermissionRefused(); +} diff --git a/app/src/main/java/io/lbry/browser/listener/TagListener.java b/app/src/main/java/io/lbry/browser/listener/TagListener.java new file mode 100644 index 00000000..4e5071bb --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/TagListener.java @@ -0,0 +1,8 @@ +package io.lbry.browser.listener; + +import io.lbry.browser.model.Tag; + +public interface TagListener { + void onTagAdded(Tag tag); + void onTagRemoved(Tag tag); +} diff --git a/app/src/main/java/io/lbry/browser/listener/WalletBalanceListener.java b/app/src/main/java/io/lbry/browser/listener/WalletBalanceListener.java new file mode 100644 index 00000000..5e0ede06 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/WalletBalanceListener.java @@ -0,0 +1,7 @@ +package io.lbry.browser.listener; + +import io.lbry.browser.model.WalletBalance; + +public interface WalletBalanceListener { + void onWalletBalanceUpdated(WalletBalance walletBalance); +} diff --git a/app/src/main/java/io/lbry/browser/listener/WalletSyncListener.java b/app/src/main/java/io/lbry/browser/listener/WalletSyncListener.java new file mode 100644 index 00000000..fcee590b --- /dev/null +++ b/app/src/main/java/io/lbry/browser/listener/WalletSyncListener.java @@ -0,0 +1,8 @@ +package io.lbry.browser.listener; + +public interface WalletSyncListener { + void onWalletSyncProcessing(); + void onWalletSyncWaitingForInput(); + void onWalletSyncEnabled(); + void onWalletSyncFailed(Exception error); +} diff --git a/app/src/main/java/io/lbry/browser/model/Claim.java b/app/src/main/java/io/lbry/browser/model/Claim.java new file mode 100644 index 00000000..31e8b5fb --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/Claim.java @@ -0,0 +1,490 @@ +package io.lbry.browser.model; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Predefined; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class Claim { + public static final String CLAIM_TYPE_CLAIM = "claim"; + public static final String CLAIM_TYPE_UPDATE = "update"; + public static final String CLAIM_TYPE_SUPPORT = "support"; + + public static final String TYPE_STREAM = "stream"; + public static final String TYPE_CHANNEL = "channel"; + public static final String TYPE_REPOST = "repost"; + + public static final String STREAM_TYPE_AUDIO = "audio"; + public static final String STREAM_TYPE_IMAGE = "image"; + public static final String STREAM_TYPE_VIDEO = "video"; + public static final String STREAM_TYPE_SOFTWARE = "software"; + + public static final String ORDER_BY_EFFECTIVE_AMOUNT = "effective_amount"; + public static final String ORDER_BY_RELEASE_TIME = "release_time"; + public static final String ORDER_BY_TRENDING_GROUP = "trending_group"; + public static final String ORDER_BY_TRENDING_MIXED = "trending_mixed"; + + public static final List CLAIM_TYPES = Arrays.asList(TYPE_CHANNEL, TYPE_STREAM); + public static final List STREAM_TYPES = Arrays.asList( + STREAM_TYPE_AUDIO, STREAM_TYPE_IMAGE, STREAM_TYPE_SOFTWARE, STREAM_TYPE_VIDEO + ); + + public static final String RELEASE_TIME_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + + @EqualsAndHashCode.Include + private boolean placeholder; + private boolean placeholderAnonymous; + private boolean featured; + private boolean unresolved; // used for featured + private String address; + private String amount; + private String canonicalUrl; + @EqualsAndHashCode.Include + private String claimId; + private int claimSequence; + private String claimOp; + private long confirmations; + private boolean decodedClaim; + private long timestamp; + private long height; + private boolean isMine; + private String name; + private String normalizedName; + private int nout; + private String permanentUrl; + private String shortUrl; + private String txid; + private String type; // claim | update | support + private String valueType; // stream | channel | repost + private Claim repostedClaim; + private Claim signingChannel; + private String repostChannelUrl; + private boolean isChannelSignatureValid; + private GenericMetadata value; + private LbryFile file; // associated file if it exists + + // device it was viewed on (for view history) + private String device; + + public static Claim claimFromOutput(JSONObject item) { + // we only need name, permanent_url, txid and nout + Claim claim = new Claim(); + claim.setClaimId(Helper.getJSONString("claim_id", null, item)); + claim.setName(Helper.getJSONString("name", null, item)); + claim.setPermanentUrl(Helper.getJSONString("permanent_url", null, item)); + claim.setTxid(Helper.getJSONString("txid", null, item)); + claim.setNout(Helper.getJSONInt("nout", -1, item)); + return claim; + } + + public String getOutpoint() { + return String.format("%s:%d", txid, nout); + } + + public boolean isFree() { + if (!(value instanceof StreamMetadata)) { + return true; + } + + Fee fee = ((StreamMetadata) value).getFee(); + return fee == null || Helper.parseDouble(fee.getAmount(), 0) == 0; + } + + public BigDecimal getActualCost(double usdRate) { + if (!(value instanceof StreamMetadata)) { + return new BigDecimal(0); + } + + Fee fee = ((StreamMetadata) value).getFee(); + if (fee != null) { + double amount = Helper.parseDouble(fee.getAmount(), 0); + if ("usd".equalsIgnoreCase(fee.getCurrency())) { + return new BigDecimal(String.valueOf(amount / usdRate)); + } + + return new BigDecimal(String.valueOf(amount)); // deweys + } + + return new BigDecimal(0); + } + + public String getMediaType() { + if (value instanceof StreamMetadata) { + StreamMetadata metadata = (StreamMetadata) value; + String mediaType = metadata.getSource() != null ? metadata.getSource().getMediaType() : null; + return mediaType; + } + return null; + } + + public boolean isPlayable() { + if (value instanceof StreamMetadata) { + StreamMetadata metadata = (StreamMetadata) value; + String mediaType = metadata.getSource() != null ? metadata.getSource().getMediaType() : null; + if (mediaType != null) { + return mediaType.startsWith("video") || mediaType.startsWith("audio"); + } + } + return false; + } + public boolean isViewable() { + if (value instanceof StreamMetadata) { + StreamMetadata metadata = (StreamMetadata) value; + String mediaType = metadata.getSource() != null ? metadata.getSource().getMediaType() : null; + if (mediaType != null) { + return mediaType.startsWith("image") || mediaType.startsWith("text"); + } + } + return false; + } + public boolean isMature() { + List tags = getTags(); + if (tags != null && tags.size() > 0) { + for (String tag : tags) { + if (Predefined.MATURE_TAGS.contains(tag.toLowerCase())) { + return true; + } + } + } + return false; + } + + public String getThumbnailUrl() { + if (value != null && value.getThumbnail() != null) { + return value.getThumbnail().getUrl(); + } + return null; + } + + public String getCoverUrl() { + if (TYPE_CHANNEL.equals(valueType) && value != null && value instanceof ChannelMetadata && ((ChannelMetadata) value).getCover() != null) { + return ((ChannelMetadata) value).getCover().getUrl(); + } + return null; + } + + public String getFirstCharacter() { + if (name != null) { + return name.startsWith("@") ? name.substring(1) : name; + } + return ""; + } + + public String getFirstTag() { + if (value != null && value.tags != null && value.tags.size() > 0) { + return value.tags.get(0); + } + return null; + } + + public String getDescription() { + return (value != null) ? value.getDescription() : null; + } + + public String getWebsiteUrl() { + return (value instanceof ChannelMetadata) ? ((ChannelMetadata) value).getWebsiteUrl() : null; + } + + public String getEmail() { + return (value instanceof ChannelMetadata) ? ((ChannelMetadata) value).getEmail() : null; + } + + public String getPublisherName() { + if (signingChannel != null) { + return signingChannel.getName(); + } + return "Anonymous"; + } + + public String getPublisherTitle() { + if (signingChannel != null) { + return Helper.isNullOrEmpty(signingChannel.getTitle()) ? signingChannel.getName() : signingChannel.getTitle(); + } + return "Anonymous"; + } + + + public List getTags() { + return (value != null && value.getTags() != null) ? new ArrayList<>(value.getTags()) : new ArrayList<>(); + } + + public List getTagObjects() { + List tags = new ArrayList<>(); + if (value != null && value.getTags() != null) { + for (String value : value.getTags()) { + tags.add(new Tag(value)); + } + } + return tags; + } + + public String getTitle() { + return (value != null) ? value.getTitle() : null; + } + public String getTitleOrName() { + return (value != null) ? value.getTitle() : getName(); + } + + public long getDuration() { + if (value instanceof StreamMetadata) { + StreamMetadata metadata = (StreamMetadata) value; + if (STREAM_TYPE_VIDEO.equalsIgnoreCase(metadata.getStreamType()) && metadata.getVideo() != null) { + return metadata.getVideo().getDuration(); + } else if (STREAM_TYPE_AUDIO.equalsIgnoreCase(metadata.getStreamType()) && metadata.getAudio() != null) { + return metadata.getAudio().getDuration(); + } + } + + return 0; + } + + public static Claim fromViewHistory(ViewHistory viewHistory) { + // only for stream claims + Claim claim = new Claim(); + claim.setClaimId(viewHistory.getClaimId()); + claim.setName(viewHistory.getClaimName()); + claim.setValueType(TYPE_STREAM); + claim.setPermanentUrl(viewHistory.getUri().toString()); + claim.setDevice(viewHistory.getDevice()); + claim.setConfirmations(1); + + StreamMetadata value = new StreamMetadata(); + value.setTitle(viewHistory.getTitle()); + value.setReleaseTime(viewHistory.getReleaseTime()); + if (!Helper.isNullOrEmpty(viewHistory.getThumbnailUrl())) { + Resource thumbnail = new Resource(); + thumbnail.setUrl(viewHistory.getThumbnailUrl()); + value.setThumbnail(thumbnail); + } + if (viewHistory.getCost() != null && viewHistory.getCost().doubleValue() > 0) { + Fee fee = new Fee(); + fee.setAmount(String.valueOf(viewHistory.getCost().doubleValue())); + fee.setCurrency(viewHistory.getCurrency()); + value.setFee(fee); + } + + claim.setValue(value); + + if (!Helper.isNullOrEmpty(viewHistory.getPublisherClaimId())) { + Claim signingChannel = new Claim(); + signingChannel.setClaimId(viewHistory.getPublisherClaimId()); + signingChannel.setName(viewHistory.getPublisherName()); + if (!Helper.isNullOrEmpty(viewHistory.getPublisherTitle())) { + GenericMetadata channelValue = new GenericMetadata(); + channelValue.setTitle(viewHistory.getPublisherTitle()); + signingChannel.setValue(channelValue); + } + claim.setSigningChannel(signingChannel); + } + + return claim; + } + + public static Claim fromJSONObject(JSONObject claimObject) { + Claim claim = null; + String claimJson = claimObject.toString(); + Type type = new TypeToken(){}.getType(); + Type streamMetadataType = new TypeToken(){}.getType(); + Type channelMetadataType = new TypeToken(){}.getType(); + + Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + claim = gson.fromJson(claimJson, type); + + try { + String valueType = claim.getValueType(); + // Specific value type parsing + if (TYPE_REPOST.equalsIgnoreCase(valueType)) { + JSONObject repostedClaimObject = claimObject.getJSONObject("reposted_claim"); + claim.setRepostedClaim(Claim.fromJSONObject(repostedClaimObject)); + } else { + JSONObject value = claimObject.getJSONObject("value"); + if (value != null) { + String valueJson = value.toString(); + if (TYPE_STREAM.equalsIgnoreCase(valueType)) { + claim.setValue(gson.fromJson(valueJson, streamMetadataType)); + } else if (TYPE_CHANNEL.equalsIgnoreCase(valueType)) { + claim.setValue(gson.fromJson(valueJson, channelMetadataType)); + } + } + } + } catch (JSONException ex) { + // pass + } + + return claim; + } + + public static Claim fromSearchJSONObject(JSONObject searchResultObject) { + Claim claim = new Claim(); + LbryUri claimUri = new LbryUri(); + try { + claim.setClaimId(searchResultObject.getString("claimId")); + claim.setName(searchResultObject.getString("name")); + claim.setConfirmations(1); + + if (claim.getName().startsWith("@")) { + claimUri.setChannelClaimId(claim.getClaimId()); + claimUri.setChannelName(claim.getName()); + claim.setValueType(TYPE_CHANNEL); + } else { + claimUri.setStreamClaimId(claim.getClaimId()); + claimUri.setStreamName(claim.getName()); + claim.setValueType(TYPE_STREAM); + } + + int duration = searchResultObject.isNull("duration") ? 0 : searchResultObject.getInt("duration"); + long feeAmount = searchResultObject.isNull("fee") ? 0 : searchResultObject.getLong("fee"); + String releaseTimeString = !searchResultObject.isNull("release_time") ? searchResultObject.getString("release_time") : null; + long releaseTime = 0; + try { + releaseTime = Double.valueOf(new SimpleDateFormat(RELEASE_TIME_DATE_FORMAT).parse(releaseTimeString).getTime() / 1000.0).longValue(); + } catch (ParseException ex) { + // pass + } + + GenericMetadata metadata = (duration > 0 || releaseTime > 0 || feeAmount > 0) ? new StreamMetadata() : new GenericMetadata(); + metadata.setTitle(searchResultObject.getString("title")); + if (metadata instanceof StreamMetadata) { + StreamInfo streamInfo = new StreamInfo(); + if (duration > 0) { + // assume stream type video + ((StreamMetadata) metadata).setStreamType(STREAM_TYPE_VIDEO); + streamInfo.setDuration(duration); + } + + Fee fee = null; + if (feeAmount > 0) { + fee = new Fee(); + fee.setAmount(String.valueOf(new BigDecimal(String.valueOf(feeAmount)).divide(new BigDecimal(100000000)))); + fee.setCurrency("LBC"); + } + + ((StreamMetadata) metadata).setFee(fee); + ((StreamMetadata) metadata).setVideo(streamInfo); + ((StreamMetadata) metadata).setReleaseTime(releaseTime); + } + claim.setValue(metadata); + + if (!searchResultObject.isNull("thumbnail_url")) { + Resource thumbnail = new Resource(); + thumbnail.setUrl(searchResultObject.getString("thumbnail_url")); + claim.getValue().setThumbnail(thumbnail); + } + + if (!searchResultObject.isNull("channel_claim_id") && !searchResultObject.isNull("channel")) { + Claim signingChannel = new Claim(); + signingChannel.setClaimId(searchResultObject.getString("channel_claim_id")); + signingChannel.setName(searchResultObject.getString("channel")); + LbryUri channelUri = new LbryUri(); + channelUri.setChannelClaimId(signingChannel.getClaimId()); + channelUri.setChannelName(signingChannel.getName()); + signingChannel.setPermanentUrl(channelUri.toString()); + + claim.setSigningChannel(signingChannel); + } + } catch (JSONException ex) { + // pass + } + + claim.setPermanentUrl(claimUri.toString()); + + return claim; + } + + @Data + public static class Meta { + private long activationHeight; + private int claimsInChannel; + private int creationHeight; + private int creationTimestamp; + private String effectiveAmount; + private long expirationHeight; + private boolean isControlling; + private String supportAmount; + private int reposted; + private double trendingGlobal; + private double trendingGroup; + private double trendingLocal; + private double trendingMixed; + } + + @Data + public static class GenericMetadata { + private String title; + private String description; + private Resource thumbnail; + private List languages; + private List tags; + private List locations; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class ChannelMetadata extends GenericMetadata { + private String publicKey; + private String publicKeyId; + private Resource cover; + private String email; + private String websiteUrl; + private List featured; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class StreamMetadata extends GenericMetadata { + private String license; + private String licenseUrl; + private long releaseTime; + private String author; + private Fee fee; + private String streamType; // video | audio | image | software + private Source source; + private StreamInfo video; + private StreamInfo audio; + private StreamInfo image; + private StreamInfo software; + + @Data + public static class Source { + private String sdHash; + private String mediaType; + private String hash; + private String name; + private long size; + } + } + + // only support "url" for now + @Data + public static class Resource { + private String url; + } + + @Data + public static class StreamInfo { + private long duration; // video / audio + private long height; // video / image + private long width; // video / image + private String os; // software + } +} diff --git a/app/src/main/java/io/lbry/browser/model/ClaimCacheKey.java b/app/src/main/java/io/lbry/browser/model/ClaimCacheKey.java new file mode 100644 index 00000000..6f8c868b --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/ClaimCacheKey.java @@ -0,0 +1,68 @@ +package io.lbry.browser.model; + +import androidx.annotation.Nullable; + +import io.lbry.browser.utils.Helper; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * Class to represent a key to check equality with another object + */ +@ToString +public class ClaimCacheKey { + @Getter + @Setter + private String claimId; + @Getter + @Setter + private String url; + + public static ClaimCacheKey fromClaimShortUrl(Claim claim) { + ClaimCacheKey key = new ClaimCacheKey(); + key.setUrl(claim.getShortUrl()); + return key; + } + + public static ClaimCacheKey fromClaimPermanentUrl(Claim claim) { + ClaimCacheKey key = new ClaimCacheKey(); + key.setUrl(claim.getPermanentUrl()); + return key; + } + + public static ClaimCacheKey fromClaim(Claim claim) { + ClaimCacheKey key = new ClaimCacheKey(); + key.setClaimId(claim.getClaimId()); + key.setUrl(!Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl()); + return key; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == null || !(obj instanceof ClaimCacheKey)) { + return false; + } + ClaimCacheKey key = (ClaimCacheKey) obj; + if (!Helper.isNullOrEmpty(claimId) && !Helper.isNullOrEmpty(key.getClaimId())) { + return claimId.equalsIgnoreCase(key.getClaimId()); + } + if (!Helper.isNullOrEmpty(url) && !Helper.isNullOrEmpty(key.getUrl())) { + return url.equalsIgnoreCase(key.getUrl()); + } + return false; + } + + @Override + public int hashCode() { + if (!Helper.isNullOrEmpty(url)) { + return url.hashCode(); + } + if (!Helper.isNullOrEmpty(claimId)) { + return claimId.hashCode(); + } + + return super.hashCode(); + } +} + diff --git a/app/src/main/java/io/lbry/browser/model/ClaimSearchCacheValue.java b/app/src/main/java/io/lbry/browser/model/ClaimSearchCacheValue.java new file mode 100644 index 00000000..60260add --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/ClaimSearchCacheValue.java @@ -0,0 +1,26 @@ +package io.lbry.browser.model; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +public class ClaimSearchCacheValue { + @Getter + @Setter + private List claims; + @Getter + @Setter + private long timestamp; + + public ClaimSearchCacheValue(List claims, long timestamp) { + this.claims = new ArrayList<>(claims); + this.timestamp = timestamp; + } + + public boolean isExpired(long ttl) { + return System.currentTimeMillis() - timestamp > ttl; + } +} diff --git a/app/src/main/java/io/lbry/browser/model/EditorsChoiceItem.java b/app/src/main/java/io/lbry/browser/model/EditorsChoiceItem.java new file mode 100644 index 00000000..49565d7a --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/EditorsChoiceItem.java @@ -0,0 +1,23 @@ +package io.lbry.browser.model; + +import lombok.Data; + +@Data +public class EditorsChoiceItem { + private boolean header; + private String title; + private String parent; + private String description; + private String thumbnailUrl; + private String permanentUrl; + + public static EditorsChoiceItem fromClaim(Claim claim) { + EditorsChoiceItem item = new EditorsChoiceItem(); + item.setTitle(claim.getTitle()); + item.setDescription(claim.getDescription()); + item.setThumbnailUrl(claim.getThumbnailUrl()); + item.setPermanentUrl(claim.getPermanentUrl()); + + return item; + } +} diff --git a/app/src/main/java/io/lbry/browser/model/Fee.java b/app/src/main/java/io/lbry/browser/model/Fee.java new file mode 100644 index 00000000..12fbc61d --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/Fee.java @@ -0,0 +1,10 @@ +package io.lbry.browser.model; + +import lombok.Data; + +@Data +public class Fee { + private String amount; + private String currency; + private String address; +} diff --git a/app/src/main/java/io/lbry/browser/model/GalleryItem.java b/app/src/main/java/io/lbry/browser/model/GalleryItem.java new file mode 100644 index 00000000..43feee58 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/GalleryItem.java @@ -0,0 +1,13 @@ +package io.lbry.browser.model; + +import lombok.Data; + +@Data +public class GalleryItem { + private String id; + private String name; + private String filePath; + private String type; + private String thumbnailPath; + private long duration; +} diff --git a/app/src/main/java/io/lbry/browser/model/Language.java b/app/src/main/java/io/lbry/browser/model/Language.java new file mode 100644 index 00000000..e3f4567b --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/Language.java @@ -0,0 +1,16 @@ +package io.lbry.browser.model; + +import lombok.Data; + +@Data +public class Language { + private String code; + private String name; + private int stringResourceId; + + public Language(String code, String name, int stringResourceId) { + this.code = code; + this.name = name; + this.stringResourceId = stringResourceId; + } +} diff --git a/app/src/main/java/io/lbry/browser/model/LbryFile.java b/app/src/main/java/io/lbry/browser/model/LbryFile.java new file mode 100644 index 00000000..ed9cb1fb --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/LbryFile.java @@ -0,0 +1,92 @@ +package io.lbry.browser.model; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import org.json.JSONObject; + +import java.lang.reflect.Type; + +import io.lbry.browser.utils.LbryUri; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class LbryFile { + private Claim.StreamMetadata metadata; + private long addedOn; + private int blobsCompleted; + private int blobsInStream; + private int blobsRemaining; + private String channelClaimId; + private String channelName; + @EqualsAndHashCode.Include + private String claimId; + private String claimName; + private boolean completed; + private String downloadDirectory; + private String downloadPath; + private String fileName; + private String key; + private String mimeType; + private int nout; + private String outpoint; + private int pointsPaid; + private String protobuf; + private String sdHash; + private String status; + private boolean stopped; + private String streamHash; + private String streamName; + private String streamingUrl; + private String suggestedFileName; + private long timestamp; + private long totalBytes; + private long totalBytesLowerBound; + private String txid; + private long writtenBytes; + + private Claim generatedClaim; + + public Claim getClaim() { + if (generatedClaim != null) { + return generatedClaim; + } + + generatedClaim = new Claim(); + generatedClaim.setValueType(Claim.TYPE_STREAM); + generatedClaim.setPermanentUrl(LbryUri.tryParse(String.format("%s#%s", claimName, claimId)).toString()); + generatedClaim.setClaimId(claimId); + generatedClaim.setName(claimName); + generatedClaim.setValue(metadata); + generatedClaim.setConfirmations(1); + generatedClaim.setTxid(txid); + generatedClaim.setNout(nout); + generatedClaim.setFile(this); + + if (channelClaimId != null) { + Claim signingChannel = new Claim(); + signingChannel.setClaimId(channelClaimId); + signingChannel.setName(channelName); + signingChannel.setPermanentUrl(LbryUri.tryParse(String.format("%s#%s", claimName, claimId)).toString()); + generatedClaim.setSigningChannel(signingChannel); + } + + return generatedClaim; + } + + public static LbryFile fromJSONObject(JSONObject fileObject) { + String fileJson = fileObject.toString(); + Type type = new TypeToken(){}.getType(); + Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + LbryFile file = gson.fromJson(fileJson, type); + + if (file.getMetadata() != null && file.getMetadata().getReleaseTime() == 0) { + file.getMetadata().setReleaseTime(file.getTimestamp()); + } + return file; + } +} diff --git a/app/src/main/java/io/lbry/browser/model/License.java b/app/src/main/java/io/lbry/browser/model/License.java new file mode 100644 index 00000000..b278eafa --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/License.java @@ -0,0 +1,20 @@ +package io.lbry.browser.model; + +import lombok.Data; + +@Data +public class License { + private String name; + private String url; + private int stringResourceId; + + public License(String name, int stringResourceId) { + this.name = name; + this.stringResourceId = stringResourceId; + } + public License(String name, String url, int stringResourceId) { + this.name = name; + this.url = url; + this.stringResourceId = stringResourceId; + } +} diff --git a/app/src/main/java/io/lbry/browser/model/Location.java b/app/src/main/java/io/lbry/browser/model/Location.java new file mode 100644 index 00000000..e1673424 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/Location.java @@ -0,0 +1,13 @@ +package io.lbry.browser.model; + +import lombok.Data; + +@Data +public class Location { + private double latitude; + private double longitude; + private String country; + private String state; + private String city; + private String code; +} diff --git a/app/src/main/java/io/lbry/browser/model/NavMenuItem.java b/app/src/main/java/io/lbry/browser/model/NavMenuItem.java new file mode 100644 index 00000000..53468b09 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/NavMenuItem.java @@ -0,0 +1,68 @@ +package io.lbry.browser.model; + +import android.content.Context; + +import androidx.core.content.res.ResourcesCompat; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Data; + +@Data +public class NavMenuItem { + public static final int ID_GROUP_FIND_CONTENT = 100; + public static final int ID_GROUP_YOUR_CONTENT = 200; + public static final int ID_GROUP_WALLET = 300; + public static final int ID_GROUP_OTHER = 400; + + // Find Content + public static final int ID_ITEM_FOLLOWING = 101; + public static final int ID_ITEM_EDITORS_CHOICE = 102; + public static final int ID_ITEM_ALL_CONTENT = 103; + + // Your Content + public static final int ID_ITEM_CHANNELS = 201; + public static final int ID_ITEM_LIBRARY = 202; + public static final int ID_ITEM_PUBLISHES = 203; + public static final int ID_ITEM_NEW_PUBLISH = 204; + + // Wallet + public static final int ID_ITEM_WALLET = 301; + public static final int ID_ITEM_REWARDS = 302; + public static final int ID_ITEM_INVITES = 303; + + // Other + public static final int ID_ITEM_SETTINGS = 401; + public static final int ID_ITEM_ABOUT = 402; + + private Context context; + private int id; + private boolean group; + private int icon; + private String title; + private String extraLabel; + private String name; // same as title, but only as en lang for events + private List items; + + public NavMenuItem(int id, int titleResourceId, boolean group, Context context) { + this.context = context; + this.id = id; + this.group = group; + + if (titleResourceId > 0) { + this.title = context.getString(titleResourceId); + } + if (group) { + this.items = new ArrayList<>(); + } + } + + public NavMenuItem(int id, int iconStringId, int titleResourceId, String name, Context context) { + this.context = context; + this.id = id; + this.icon = iconStringId; + this.title = context.getString(titleResourceId); + this.name = name; + } +} diff --git a/app/src/main/java/io/lbry/browser/model/Tag.java b/app/src/main/java/io/lbry/browser/model/Tag.java new file mode 100644 index 00000000..6f898bea --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/Tag.java @@ -0,0 +1,44 @@ +package io.lbry.browser.model; + +import java.util.Comparator; + +import io.lbry.browser.utils.Predefined; +import lombok.Getter; +import lombok.Setter; + +public class Tag implements Comparator { + @Getter + @Setter + private String name; + @Getter + @Setter + private boolean followed; + + public Tag() { + + } + public Tag(String name) { + this.name = name; + } + + public String getLowercaseName() { + return name.toLowerCase(); + } + + public boolean isMature() { + return Predefined.MATURE_TAGS.contains(name.toLowerCase()); + } + + public String toString() { + return getLowercaseName(); + } + public boolean equals(Object o) { + return (o instanceof Tag) && ((Tag) o).getName().equalsIgnoreCase(name); + } + public int hashCode() { + return name.toLowerCase().hashCode(); + } + public int compare(Tag a, Tag b) { + return a.getLowercaseName().compareToIgnoreCase(b.getLowercaseName()); + } +} diff --git a/app/src/main/java/io/lbry/browser/model/Transaction.java b/app/src/main/java/io/lbry/browser/model/Transaction.java new file mode 100644 index 00000000..e48f3913 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/Transaction.java @@ -0,0 +1,133 @@ +package io.lbry.browser.model; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigDecimal; +import java.util.Date; + +import io.lbry.browser.R; +import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryUri; +import lombok.Data; + +@Data +public class Transaction { + private int confirmations; + private Date txDate; + private String date; + private String claim; + private String claimId; + private String txid; + private BigDecimal value; + private BigDecimal fee; + private long timestamp; + private int descriptionStringId; + private TransactionInfo abandonInfo; + private TransactionInfo claimInfo; + private TransactionInfo supportInfo; + private TransactionInfo updateInfo; + + public LbryUri getClaimUrl() { + if (!Helper.isNullOrEmpty(claim) && !Helper.isNullOrEmpty(claimId)) { + try { + return LbryUri.parse(LbryUri.normalize(String.format("%s#%s", claim, claimId))); + } catch (LbryUriException ex) { + // pass + } + } + return null; + } + + public static Transaction fromJSONObject(JSONObject jsonObject) { + Transaction transaction = new Transaction(); + transaction.setConfirmations(Helper.getJSONInt("confirmations", -1, jsonObject)); + transaction.setDate(Helper.getJSONString("date", null, jsonObject)); + transaction.setTxid(Helper.getJSONString("txid", null, jsonObject)); + transaction.setValue(new BigDecimal(Helper.getJSONString("value", "0", jsonObject))); + transaction.setFee(new BigDecimal(Helper.getJSONString("fee", "0", jsonObject))); + transaction.setTimestamp(Helper.getJSONLong("timestamp", 0, jsonObject) * 1000); + transaction.setTxDate(new Date(transaction.getTimestamp())); + + int descStringId = -1; + TransactionInfo info = null; + try { + if (jsonObject.has("abandon_info")) { + JSONArray array = jsonObject.getJSONArray("abandon_info"); + if (array.length() > 0) { + info = TransactionInfo.fromJSONObject(array.getJSONObject(0)); + descStringId = R.string.abandon; + transaction.setAbandonInfo(info); + } + } + if (info == null && jsonObject.has("claim_info")) { + JSONArray array = jsonObject.getJSONArray("claim_info"); + if (array.length() > 0) { + info = TransactionInfo.fromJSONObject(array.getJSONObject(0)); + descStringId = info.getClaimName().startsWith("@") ? R.string.channel : R.string.publish; + transaction.setClaimInfo(info); + } + } + if (info == null && jsonObject.has("support_info")) { + JSONArray array = jsonObject.getJSONArray("support_info"); + if (array.length() > 0) { + info = TransactionInfo.fromJSONObject(array.getJSONObject(0)); + descStringId = info.isTip() ? R.string.tip : R.string.support; + transaction.setSupportInfo(info); + } + } + if (info == null && jsonObject.has("update_info")) { + JSONArray array = jsonObject.getJSONArray("update_info"); + if (array.length() > 0) { + info = TransactionInfo.fromJSONObject(array.getJSONObject(0)); + descStringId = info.getClaimName().startsWith("@") ? R.string.channel_update : R.string.publish_update; + transaction.setUpdateInfo(info); + } + } + if (info != null) { + transaction.setClaim(info.getClaimName()); + transaction.setClaimId(info.getClaimId()); + } + } catch (JSONException ex) { + // pass + } + + if (transaction.getValue().doubleValue() == 0 && info != null && info.getBalanceDelta().doubleValue() != 0) { + transaction.setValue(info.getBalanceDelta()); + } + + if (descStringId == -1) { + descStringId = transaction.getValue().signum() == -1 || transaction.getFee().signum() == -1 ? R.string.spend : R.string.receive; + } + transaction.setDescriptionStringId(descStringId); + + return transaction; + } + + @Data + public static class TransactionInfo { + private String address; + private BigDecimal amount; + private BigDecimal balanceDelta; + private String claimId; + private String claimName; + private boolean isTip; + private int nout; + + public static TransactionInfo fromJSONObject(JSONObject jsonObject) { + TransactionInfo info = new TransactionInfo(); + + info.setAddress(Helper.getJSONString("address", null, jsonObject)); + info.setAmount(new BigDecimal(Helper.getJSONString("amount", "0", jsonObject))); + info.setBalanceDelta(new BigDecimal(Helper.getJSONString("balance_delta", "0", jsonObject))); + info.setClaimId(Helper.getJSONString("claim_id", null, jsonObject)); + info.setClaimName(Helper.getJSONString("claim_name", null, jsonObject)); + info.setTip(Helper.getJSONBoolean("is_tip", false, jsonObject)); + info.setNout(Helper.getJSONInt("nout", -1, jsonObject)); + + return info; + } + } +} diff --git a/app/src/main/java/io/lbry/browser/model/UrlSuggestion.java b/app/src/main/java/io/lbry/browser/model/UrlSuggestion.java new file mode 100644 index 00000000..b5ed912a --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/UrlSuggestion.java @@ -0,0 +1,55 @@ +package io.lbry.browser.model; + +import io.lbry.browser.utils.LbryUri; +import lombok.Data; + +@Data +public class UrlSuggestion { + public static final int TYPE_CHANNEL = 1; + public static final int TYPE_FILE = 2; + public static final int TYPE_SEARCH = 3; + public static final int TYPE_TAG = 4; + + private int type; + private String text; + private LbryUri uri; + private Claim claim; // associated claim if resolved + private boolean titleTextOnly; + private boolean titleUrlOnly; + private boolean useTextAsDescription; + + public UrlSuggestion() { + + } + public UrlSuggestion(int type, String text) { + this.type = type; + this.text = text; + } + public UrlSuggestion(int type, String text, LbryUri uri) { + this(type, text); + this.uri = uri; + } + public UrlSuggestion(int type, String text, LbryUri uri, boolean titleTextOnly) { + this(type, text, uri); + this.titleTextOnly = titleTextOnly; + } + + public String getTitle() { + if (titleUrlOnly && (type == TYPE_CHANNEL || type == TYPE_FILE)) { + return uri.toString(); + } + + if (!titleTextOnly) { + switch (type) { + case TYPE_CHANNEL: + return String.format("%s - %s", text.startsWith("@") ? text.substring(1) : text, uri.toVanityString()); + case TYPE_FILE: + return String.format("%s - %s", text, uri.toVanityString()); + case TYPE_TAG: + return String.format("%s - #%s", text, text); + } + } + + return text; + } +} diff --git a/app/src/main/java/io/lbry/browser/model/ViewHistory.java b/app/src/main/java/io/lbry/browser/model/ViewHistory.java new file mode 100644 index 00000000..ea668615 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/ViewHistory.java @@ -0,0 +1,64 @@ +package io.lbry.browser.model; + +import java.math.BigDecimal; +import java.util.Date; + +import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; +import lombok.Data; + +@Data +public class ViewHistory { + private LbryUri uri; + private String claimId; + private String claimName; + private BigDecimal cost; + private String currency; + private String title; + private String publisherClaimId; + private String publisherName; + private String publisherTitle; + private String thumbnailUrl; + private String device; + private long releaseTime; + private Date timestamp; + + public static ViewHistory fromClaimWithUrlAndDeviceName(Claim claim, String url, String deviceName) { + ViewHistory history = new ViewHistory(); + LbryUri uri = LbryUri.tryParse(url); + if (uri == null) { + uri = LbryUri.tryParse(claim.getPermanentUrl()); + } + history.setUri(uri); + history.setClaimId(claim.getClaimId()); + history.setClaimName(claim.getName()); + history.setTitle(claim.getTitle()); + history.setThumbnailUrl(claim.getThumbnailUrl()); + + Claim.GenericMetadata metadata = claim.getValue(); + if (metadata instanceof Claim.StreamMetadata) { + Claim.StreamMetadata value = (Claim.StreamMetadata) metadata; + history.setReleaseTime(value.getReleaseTime()); + if (value.getFee() != null) { + Fee fee = value.getFee(); + history.setCost(new BigDecimal(fee.getAmount())); + history.setCurrency(fee.getCurrency()); + } + } + if (history.getReleaseTime() == 0) { + history.setReleaseTime(claim.getTimestamp()); + } + + Claim signingChannel = claim.getSigningChannel(); + if (signingChannel != null) { + history.setPublisherClaimId(signingChannel.getClaimId()); + history.setPublisherName(signingChannel.getName()); + history.setPublisherTitle(signingChannel.getTitle()); + } + + history.setDevice(deviceName); + + return history; + } +} diff --git a/app/src/main/java/io/lbry/browser/model/WalletBalance.java b/app/src/main/java/io/lbry/browser/model/WalletBalance.java new file mode 100644 index 00000000..993a0c31 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/WalletBalance.java @@ -0,0 +1,24 @@ +package io.lbry.browser.model; + +import java.math.BigDecimal; + +import lombok.Data; + +@Data +public class WalletBalance { + private BigDecimal available; + private BigDecimal reserved; + private BigDecimal claims; + private BigDecimal supports; + private BigDecimal tips; + private BigDecimal total; + + public WalletBalance() { + available = new BigDecimal(0); + reserved = new BigDecimal(0); + claims = new BigDecimal(0); + supports = new BigDecimal(0); + tips = new BigDecimal(0); + total = new BigDecimal(0); + } +} diff --git a/app/src/main/java/io/lbry/browser/model/WalletSync.java b/app/src/main/java/io/lbry/browser/model/WalletSync.java new file mode 100644 index 00000000..05857ff0 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/WalletSync.java @@ -0,0 +1,20 @@ +package io.lbry.browser.model; + +import lombok.Data; + +@Data +public class WalletSync { + private String hash; + private String data; + private boolean changed; + + public WalletSync(String hash, String data) { + this.hash = hash; + this.data = data; + } + + public WalletSync(String hash, String data, boolean changed) { + this(hash, data); + this.changed = changed; + } +} diff --git a/app/src/main/java/io/lbry/browser/model/lbryinc/Invitee.java b/app/src/main/java/io/lbry/browser/model/lbryinc/Invitee.java new file mode 100644 index 00000000..2d84072d --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/lbryinc/Invitee.java @@ -0,0 +1,11 @@ +package io.lbry.browser.model.lbryinc; + +import lombok.Data; + +@Data +public class Invitee { + private boolean header; + private String email; + private boolean inviteRewardClaimed; + private boolean inviteRewardClaimable; +} diff --git a/app/src/main/java/io/lbry/browser/model/lbryinc/Reward.java b/app/src/main/java/io/lbry/browser/model/lbryinc/Reward.java new file mode 100644 index 00000000..67f46db6 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/lbryinc/Reward.java @@ -0,0 +1,69 @@ +package io.lbry.browser.model.lbryinc; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import org.json.JSONObject; + +import java.lang.reflect.Type; + +import io.lbry.browser.model.Claim; +import io.lbry.browser.utils.Helper; +import lombok.Data; + +@Data +public class Reward { + public static final String TYPE_NEW_DEVELOPER = "new_developer"; + public static final String TYPE_NEW_USER = "new_user"; + public static final String TYPE_CONFIRM_EMAIL = "email_provided"; + public static final String TYPE_FIRST_CHANNEL = "new_channel"; + public static final String TYPE_FIRST_STREAM = "first_stream"; + public static final String TYPE_MANY_DOWNLOADS = "many_downloads"; + public static final String TYPE_FIRST_PUBLISH = "first_publish"; + public static final String TYPE_REFERRAL = "referrer"; + public static final String TYPE_REFEREE = "referee"; + public static final String TYPE_REWARD_CODE = "reward_code"; + public static final String TYPE_SUBSCRIPTION = "subscription"; + public static final String YOUTUBE_CREATOR = "youtube_creator"; + public static final String TYPE_DAILY_VIEW = "daily_view"; + public static final String TYPE_NEW_ANDROID = "new_android"; + + private boolean custom; + private long id; + private String rewardType; + private double rewardAmount; + private String transactionId; + private String createdAt; + private String updatedAt; + private String rewardTitle; + private String rewardDescription; + private String rewardNotification; + private String rewardRange; + + public String getDisplayAmount() { + if (shouldDisplayRange()) { + return rewardRange.split("-")[1]; + } + if (rewardAmount > 0) { + return String.valueOf(rewardAmount); + } + return "?"; + } + + public boolean isClaimed() { + return !Helper.isNullOrEmpty(transactionId); + } + + public boolean shouldDisplayRange() { + return (!isClaimed() && !Helper.isNullOrEmpty(rewardRange) && rewardRange.indexOf('-') > -1); + } + + public static Reward fromJSONObject(JSONObject rewardObject) { + String rewardJson = rewardObject.toString(); + Type type = new TypeToken(){}.getType(); + Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + return gson.fromJson(rewardJson, type); + } +} diff --git a/app/src/main/java/io/lbry/browser/model/lbryinc/Subscription.java b/app/src/main/java/io/lbry/browser/model/lbryinc/Subscription.java new file mode 100644 index 00000000..30f2412a --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/lbryinc/Subscription.java @@ -0,0 +1,36 @@ +package io.lbry.browser.model.lbryinc; + +import io.lbry.browser.model.Claim; +import lombok.Getter; +import lombok.Setter; + +public class Subscription { + @Getter + @Setter + private String channelName; + @Getter + @Setter + private String url; + + public Subscription() { + + } + public Subscription(String channelName, String url) { + this.channelName = channelName; + this.url = url; + } + + public static Subscription fromClaim(Claim claim) { + return new Subscription(claim.getName(), claim.getPermanentUrl()); + } + public String toString() { + return url; + } + + public boolean equals(Object o) { + return (o instanceof Subscription) && url != null && url.equalsIgnoreCase(((Subscription) o).getUrl()); + } + public int hashCode() { + return url.toLowerCase().hashCode(); + } +} diff --git a/app/src/main/java/io/lbry/browser/model/lbryinc/User.java b/app/src/main/java/io/lbry/browser/model/lbryinc/User.java new file mode 100644 index 00000000..15c5bec2 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/lbryinc/User.java @@ -0,0 +1,42 @@ +package io.lbry.browser.model.lbryinc; + +import java.util.List; + +import lombok.Data; + +@Data +public class User { + private String createdAt; + private String familyName; + private String givenName; + private List groups; + private boolean hasVerifiedEmail; + private long id; + private boolean inviteRewardClaimed; + private String invitedAt; + private long inivtedById; + private int invitesRemaining; + private boolean isEmailEnabled; + private boolean isIdentityVerified; + private boolean isRewardApproved; + private String language; + private long manualApprovalUserId; + private String primaryEmail; + private String rewardStatusChangeTrigger; + private String updatedAt; + private List youtubeChannels; + private List deviceTypes; + + @Data + public static class YoutubeChannel { + String ytChannelName; + String lbryChannelName; + String channelClaimId; + String syncStatus; + String statusToken; + boolean transferable; + String transferState; + List publishToAddress; + String publicKey; + } +} diff --git a/app/src/main/java/io/lbry/browser/reactmodules/BackgroundMediaModule.java b/app/src/main/java/io/lbry/browser/reactmodules/BackgroundMediaModule.java deleted file mode 100644 index 38a67202..00000000 --- a/app/src/main/java/io/lbry/browser/reactmodules/BackgroundMediaModule.java +++ /dev/null @@ -1,84 +0,0 @@ -package io.lbry.browser.reactmodules; - -import android.app.Activity; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.os.Build; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.ContextCompat; - -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; - -import io.lbry.browser.MainActivity; -import io.lbry.browser.R; -import io.lbry.lbrysdk.LbrynetService; - -public class BackgroundMediaModule extends ReactContextBaseJavaModule { - - public static final int NOTIFICATION_ID = 30; - - public static final String ACTION_PLAY = "io.lbry.browser.ACTION_MEDIA_PLAY"; - - public static final String ACTION_PAUSE = "io.lbry.browser.ACTION_MEDIA_PAUSE"; - - public static final String ACTION_STOP = "io.lbry.browser.ACTION_MEDIA_STOP"; - - private Context context; - - public BackgroundMediaModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - } - - @Override - public String getName() { - return "BackgroundMedia"; - } - - @ReactMethod - public void showPlaybackNotification(String title, String publisher, String uri, boolean paused) { - Intent contextIntent = new Intent(context, MainActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contextIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - Intent playIntent = new Intent(); - playIntent.setAction(ACTION_PLAY); - PendingIntent playPendingIntent = PendingIntent.getBroadcast(context, 0, playIntent, 0); - - Intent pauseIntent = new Intent(); - pauseIntent.setAction(ACTION_PAUSE); - PendingIntent pausePendingIntent = PendingIntent.getBroadcast(context, 0, pauseIntent, 0); - - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, LbrynetService.NOTIFICATION_CHANNEL_ID); - builder.setColor(ContextCompat.getColor(context, R.color.lbryGreen)) - .setContentIntent(pendingIntent) - .setContentTitle(title) - .setContentText(publisher) - .setGroup(LbrynetService.GROUP_SERVICE) - .setOngoing(!paused) - .setSmallIcon(paused ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play) - .setStyle(new androidx.media.app.NotificationCompat.MediaStyle() - .setShowActionsInCompactView(0)) - .addAction(paused ? android.R.drawable.ic_media_play : android.R.drawable.ic_media_pause, - paused ? "Play" : "Pause", - paused ? playPendingIntent : pausePendingIntent) - .build(); - - notificationManager.notify(NOTIFICATION_ID, builder.build()); - } - - @ReactMethod - public void hidePlaybackNotification() { - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - notificationManager.cancel(NOTIFICATION_ID); - } -} diff --git a/app/src/main/java/io/lbry/browser/reactmodules/DaemonServiceControlModule.java b/app/src/main/java/io/lbry/browser/reactmodules/DaemonServiceControlModule.java deleted file mode 100644 index 715414d2..00000000 --- a/app/src/main/java/io/lbry/browser/reactmodules/DaemonServiceControlModule.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.lbry.browser.reactmodules; - -import android.app.Activity; -import android.app.NotificationChannel; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.SharedPreferences; - -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; - -import io.lbry.browser.MainActivity; -import io.lbry.lbrysdk.LbrynetService; -import io.lbry.lbrysdk.ServiceHelper; - -public class DaemonServiceControlModule extends ReactContextBaseJavaModule { - - private Context context; - - public DaemonServiceControlModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - } - - @Override - public String getName() { - return "DaemonServiceControl"; - } - - @ReactMethod - public void startService() { - ServiceHelper.start(context, "", LbrynetService.class, "lbrynetservice"); - } - - @ReactMethod - public void stopService() { - ServiceHelper.stop(context, LbrynetService.class); - } - - @ReactMethod - public void setKeepDaemonRunning(boolean value) { - if (context != null) { - SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sp.edit(); - editor.putBoolean(MainActivity.SETTING_KEEP_DAEMON_RUNNING, value); - editor.commit(); - } - } -} diff --git a/app/src/main/java/io/lbry/browser/reactmodules/FirebaseModule.java b/app/src/main/java/io/lbry/browser/reactmodules/FirebaseModule.java deleted file mode 100644 index 74dfe9cc..00000000 --- a/app/src/main/java/io/lbry/browser/reactmodules/FirebaseModule.java +++ /dev/null @@ -1,131 +0,0 @@ -package io.lbry.browser.reactmodules; - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.widget.Toast; - -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableMap; -import com.google.firebase.analytics.FirebaseAnalytics; -import com.google.android.gms.tasks.OnCompleteListener; -import com.google.android.gms.tasks.Task; -import com.google.firebase.iid.FirebaseInstanceId; -import com.google.firebase.iid.InstanceIdResult; - -import io.lbry.browser.BuildConfig; -import io.lbry.browser.MainActivity; -import io.lbry.lbrysdk.Utils; - -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import org.json.JSONObject; -import org.json.JSONException; - -public class FirebaseModule extends ReactContextBaseJavaModule { - - private Context context; - - private FirebaseAnalytics firebaseAnalytics; - - public FirebaseModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - this.firebaseAnalytics = FirebaseAnalytics.getInstance(context); - } - - @Override - public String getName() { - return "Firebase"; - } - - @ReactMethod - public void setCurrentScreen(String name, final Promise promise) { - final Activity activity = getCurrentActivity(); - if (activity != null && firebaseAnalytics != null) { - activity.runOnUiThread(new Runnable() { - public void run() { - firebaseAnalytics.setCurrentScreen(activity, name, Utils.capitalizeAndStrip(name)); - } - }); - } - promise.resolve(true); - } - - @ReactMethod - public void track(String name, ReadableMap payload) { - Bundle bundle = new Bundle(); - if (payload != null) { - HashMap payloadMap = payload.toHashMap(); - for (Map.Entry entry : payloadMap.entrySet()) { - Object value = entry.getValue(); - if (value != null) { - bundle.putString(entry.getKey(), entry.getValue().toString()); - } - } - } - - if (firebaseAnalytics != null) { - firebaseAnalytics.logEvent(name, bundle); - } - } - - @ReactMethod - public void logException(boolean fatal, String message, String error) { - Bundle bundle = new Bundle(); - bundle.putString("exception_message", message); - bundle.putString("exception_error", error); - if (firebaseAnalytics != null) { - firebaseAnalytics.logEvent(fatal ? "reactjs_exception" : "reactjs_warning", bundle); - } - - if (fatal) { - Toast.makeText(context, - "An application error occurred which has been automatically logged. " + - "If you keep seeing this message, please provide feedback to the LBRY " + - "team by emailing hello@lbry.com.", - Toast.LENGTH_LONG).show(); - } - } - - @ReactMethod - public void getMessagingToken(final Promise promise) { - FirebaseInstanceId.getInstance().getInstanceId() - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(Task task) { - if (!task.isSuccessful()) { - promise.reject("Firebase getInstanceId call failed"); - return; - } - - // Get new Instance ID token - String token = task.getResult().getToken(); - promise.resolve(token); - } - }); - } - - @ReactMethod - public void logLaunchTiming() { - Date end = new Date(); - MainActivity.LaunchTiming currentTiming = MainActivity.CurrentLaunchTiming; - if (currentTiming == null) { - // no start timing data, so skip this - return; - } - - long totalTimeMs = end.getTime() - currentTiming.getStart().getTime(); - String eventName = currentTiming.isColdStart() ? "app_cold_start" : "app_warm_start"; - Bundle bundle = new Bundle(); - bundle.putLong("total_ms", totalTimeMs); - bundle.putLong("total_seconds", new Double(Math.ceil(totalTimeMs / 1000.0)).longValue()); - if (firebaseAnalytics != null) { - firebaseAnalytics.logEvent(eventName, bundle); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/io/lbry/browser/reactmodules/FirstRunModule.java b/app/src/main/java/io/lbry/browser/reactmodules/FirstRunModule.java deleted file mode 100644 index 73212018..00000000 --- a/app/src/main/java/io/lbry/browser/reactmodules/FirstRunModule.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.lbry.browser.reactmodules; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.SharedPreferences; -import android.os.Bundle; - -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; - -import com.google.firebase.analytics.FirebaseAnalytics; - -import io.lbry.browser.MainActivity; - -public class FirstRunModule extends ReactContextBaseJavaModule { - private Context context; - - private SharedPreferences sp; - - public FirstRunModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - this.sp = reactContext.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - } - - @Override - public String getName() { - return "FirstRun"; - } - - @ReactMethod - public void isFirstRun(final Promise promise) { - // If firstRun flag does not exist, default to true - boolean firstRun = sp.getBoolean("firstRun", true); - promise.resolve(firstRun); - } - - @ReactMethod - public void firstRunCompleted() { - SharedPreferences.Editor editor = sp.edit(); - editor.putBoolean("firstRun", false); - editor.commit(); - - FirebaseAnalytics firebase = FirebaseAnalytics.getInstance(context); - if (firebase != null) { - Bundle bundle = new Bundle(); - firebase.logEvent("first_run_completed", bundle); - } - } -} diff --git a/app/src/main/java/io/lbry/browser/reactmodules/GalleryModule.java b/app/src/main/java/io/lbry/browser/reactmodules/GalleryModule.java deleted file mode 100644 index fd1e25f2..00000000 --- a/app/src/main/java/io/lbry/browser/reactmodules/GalleryModule.java +++ /dev/null @@ -1,313 +0,0 @@ -package io.lbry.browser.reactmodules; - -import android.content.Context; -import android.content.ContentResolver; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.Manifest; -import android.media.ThumbnailUtils; -import android.os.AsyncTask; -import android.os.Bundle; -import android.provider.MediaStore; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.modules.core.DeviceEventManagerModule; - -import io.lbry.browser.MainActivity; -import io.lbry.lbrysdk.Utils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.List; -import java.util.ArrayList; - -public class GalleryModule extends ReactContextBaseJavaModule { - private Context context; - - public GalleryModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - } - - @Override - public String getName() { - return "Gallery"; - } - - @ReactMethod - public void getVideos(Promise promise) { - WritableArray items = Arguments.createArray(); - List videos = loadVideos(); - for (int i = 0; i < videos.size(); i++) { - items.pushMap(videos.get(i).toMap()); - } - - promise.resolve(items); - } - - @ReactMethod - public void getThumbnailPath(Promise promise) { - if (context != null) { - File cacheDir = context.getExternalCacheDir(); - String thumbnailPath = String.format("%s/thumbnails", cacheDir.getAbsolutePath()); - promise.resolve(thumbnailPath); - return; - } - - promise.resolve(null); - } - - @ReactMethod - public void getUploadsPath(Promise promise) { - if (context != null) { - String baseFolder = Utils.getExternalStorageDir(context); - String uploadsPath = String.format("%s/LBRY/Uploads", baseFolder); - File uploadsDir = new File(uploadsPath); - if (!uploadsDir.isDirectory()) { - uploadsDir.mkdirs(); - } - promise.resolve(uploadsPath); - } - - promise.reject("The content could not be saved to the device. Please check your storage permissions."); - } - - @ReactMethod - public void createVideoThumbnail(String targetId, String filePath, Promise promise) { - (new AsyncTask() { - protected String doInBackground(Void... param) { - String thumbnailPath = null; - - if (context != null) { - Bitmap thumbnail = ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Video.Thumbnails.MINI_KIND); - File cacheDir = context.getExternalCacheDir(); - thumbnailPath = String.format("%s/thumbnails/%s.png", cacheDir.getAbsolutePath(), targetId); - - File file = new File(thumbnailPath); - try (FileOutputStream os = new FileOutputStream(thumbnailPath)) { - thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os); - os.close(); - } catch (IOException ex) { - promise.reject("Could not create a thumbnail for the video"); - return null; - } - } - - return thumbnailPath; - } - - public void onPostExecute(String thumbnailPath) { - if (thumbnailPath != null && thumbnailPath.trim().length() > 0) { - promise.resolve(thumbnailPath); - } - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - @ReactMethod - public void createImageThumbnail(String targetId, String filePath, Promise promise) { - (new AsyncTask() { - protected String doInBackground(Void... param) { - String thumbnailPath = null; - FileOutputStream os = null; - try { - Bitmap source = BitmapFactory.decodeFile(filePath); - // MINI_KIND dimensions - Bitmap thumbnail = Bitmap.createScaledBitmap(source, 512, 384, false); - - if (context != null) { - File cacheDir = context.getExternalCacheDir(); - thumbnailPath = String.format("%s/thumbnails/%s.png", cacheDir.getAbsolutePath(), targetId); - os = new FileOutputStream(thumbnailPath); - if (thumbnail != null) { - thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os); - } - os.close(); - } - } catch (IOException ex) { - promise.reject("Could not create a thumbnail for the image"); - return null; - } finally { - if (os != null) { - try { - os.close(); - } catch (IOException ex) { - // ignoe - } - } - } - - return thumbnailPath; - } - - public void onPostExecute(String thumbnailPath) { - if (thumbnailPath != null && thumbnailPath.trim().length() > 0) { - promise.resolve(thumbnailPath); - } - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private List loadVideos() { - String[] projection = { - MediaStore.MediaColumns._ID, - MediaStore.MediaColumns.DATA, - MediaStore.MediaColumns.DISPLAY_NAME, - MediaStore.MediaColumns.MIME_TYPE, - MediaStore.Video.Media.DURATION - }; - - List ids = new ArrayList(); - List items = new ArrayList(); - Cursor cursor = context.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projection, null, null, - String.format("%s DESC", MediaStore.MediaColumns.DATE_MODIFIED)); - while (cursor.moveToNext()) { - int idColumn = cursor.getColumnIndex(MediaStore.MediaColumns._ID); - int nameColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); - int typeColumn = cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE); - int pathColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DATA); - int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION); - - String id = cursor.getString(idColumn); - GalleryItem item = new GalleryItem(); - item.setId(id); - item.setName(cursor.getString(nameColumn)); - item.setType(cursor.getString(typeColumn)); - item.setFilePath(cursor.getString(pathColumn)); - items.add(item); - ids.add(id); - } - - checkThumbnails(ids); - - return items; - } - - private void checkThumbnails(final List ids) { - (new AsyncTask() { - protected Void doInBackground(Void... param) { - if (context != null) { - ContentResolver resolver = context.getContentResolver(); - for (int i = 0; i < ids.size(); i++) { - String id = ids.get(i); - File cacheDir = context.getExternalCacheDir(); - File thumbnailsDir = new File(String.format("%s/thumbnails", cacheDir.getAbsolutePath())); - if (!thumbnailsDir.isDirectory()) { - thumbnailsDir.mkdirs(); - } - - String thumbnailPath = String.format("%s/%s.png", thumbnailsDir.getAbsolutePath(), id); - File file = new File(thumbnailPath); - if (!file.exists()) { - // save the thumbnail to the path - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = 1; - Bitmap thumbnail = MediaStore.Video.Thumbnails.getThumbnail( - resolver, Long.parseLong(id), MediaStore.Video.Thumbnails.MINI_KIND, options); - if (thumbnail != null) { - try (FileOutputStream os = new FileOutputStream(thumbnailPath)) { - thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os); - } catch (IOException ex) { - // skip - } - } - } - - if (file.exists() && file.length() > 0 && GalleryModule.this.context != null) { - WritableMap params = Arguments.createMap(); - params.putString("id", id); - ((ReactApplicationContext) GalleryModule.this.context).getJSModule( - DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onGalleryThumbnailChecked", params); - } - } - } - - return null; - } - - public void onPostExecute(Void result) { - if (GalleryModule.this.context != null) { - ((ReactApplicationContext) GalleryModule.this.context).getJSModule( - DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onAllGalleryThumbnailsChecked", null); - } - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private static class GalleryItem { - private String id; - - private int duration; - - private String filePath; - - private String name; - - private String type; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public int getDuration() { - return duration; - } - - public void setDuration(int duration) { - this.duration = duration; - } - - public String getFilePath() { - return filePath; - } - - public void setFilePath(String filePath) { - this.filePath = filePath; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public WritableMap toMap() { - WritableMap map = Arguments.createMap(); - map.putString("id", id); - map.putString("name", name); - map.putString("filePath", filePath); - map.putString("type", type); - map.putInt("duration", duration); - - return map; - } - } - - @ReactMethod - public void canUseCamera(final Promise promise) { - promise.resolve(MainActivity.hasPermission(Manifest.permission.CAMERA, MainActivity.getActivity())); - } -} diff --git a/app/src/main/java/io/lbry/browser/reactmodules/RequestsModule.java b/app/src/main/java/io/lbry/browser/reactmodules/RequestsModule.java deleted file mode 100644 index a82a1707..00000000 --- a/app/src/main/java/io/lbry/browser/reactmodules/RequestsModule.java +++ /dev/null @@ -1,74 +0,0 @@ -package io.lbry.browser.reactmodules; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.Bundle; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.bridge.ReadableType; -import com.facebook.react.bridge.WritableMap; - -import io.lbry.browser.MainActivity; -import io.lbry.lbrysdk.Utils; - -import java.util.List; -import java.util.ArrayList; - -import org.json.JSONObject; -import org.json.JSONArray; -import org.json.JSONException; - -public class RequestsModule extends ReactContextBaseJavaModule { - private Context context; - - public RequestsModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - } - - @Override - public String getName() { - return "Requests"; - } - - @ReactMethod - public void get(final String url, final Promise promise) { - (new AsyncTask() { - @Override - protected String doInBackground(Void... params) { - try { - return Utils.performRequest(url); - } catch (Exception ex) { - return null; - } - } - - protected void onPostExecute(String response) { - if (response == null) { - promise.reject(String.format("Request to %s returned null.", url)); - return; - } - - promise.resolve(response); - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - - } - - @ReactMethod - public void lbryioCall(String authToken, final Promise promise) { - // get the auth token here, or let the app pass it in? - } - - @ReactMethod - public void lbryCall(final Promise promise) { - - } -} \ No newline at end of file diff --git a/app/src/main/java/io/lbry/browser/reactmodules/ScreenOrientationModule.java b/app/src/main/java/io/lbry/browser/reactmodules/ScreenOrientationModule.java deleted file mode 100644 index 58abfa27..00000000 --- a/app/src/main/java/io/lbry/browser/reactmodules/ScreenOrientationModule.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.lbry.browser.reactmodules; - -import android.app.Activity; -import android.content.Context; -import android.content.pm.ActivityInfo; - -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; - -public class ScreenOrientationModule extends ReactContextBaseJavaModule { - private Context context; - - public ScreenOrientationModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - } - - @Override - public String getName() { - return "ScreenOrientation"; - } - - @ReactMethod - public void unlockOrientation() { - Activity activity = getCurrentActivity(); - if (activity != null) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER); - } - } - - @ReactMethod - public void lockOrientationLandscape() { - Activity activity = getCurrentActivity(); - if (activity != null) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } - } - - @ReactMethod - public void lockOrientationPortrait() { - Activity activity = getCurrentActivity(); - if (activity != null) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } - } -} diff --git a/app/src/main/java/io/lbry/browser/reactmodules/StatePersistorModule.java b/app/src/main/java/io/lbry/browser/reactmodules/StatePersistorModule.java deleted file mode 100644 index 9424abcb..00000000 --- a/app/src/main/java/io/lbry/browser/reactmodules/StatePersistorModule.java +++ /dev/null @@ -1,193 +0,0 @@ -package io.lbry.browser.reactmodules; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.Bundle; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.bridge.ReadableType; -import com.facebook.react.bridge.WritableMap; - -import io.lbry.browser.MainActivity; - -import java.util.List; -import java.util.ArrayList; - -import org.json.JSONObject; -import org.json.JSONArray; -import org.json.JSONException; - -public class StatePersistorModule extends ReactContextBaseJavaModule { - private Context context; - - private List queue; - - private ReadableMap filter; - - private ReadableMap lastState; - - private AsyncTask persistTask; - - public StatePersistorModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - queue = new ArrayList(); - } - - @Override - public String getName() { - return "StatePersistor"; - } - - /*private WritableMap filterState(ReadableMap state) { - WritableMap filteredState = Arguments.createMap(); - - return state; - }*/ - - public boolean hasStateChanged(ReadableMap newState) { - return false; - } - - @ReactMethod - public void update(ReadableMap state, ReadableMap filter) { - if (this.filter == null) { - this.filter = filter; - } - // process state updates from the queue using a background task - synchronized (this) { - queue.add(state); - } - persistState(); - } - - private void persistState() { - persistState(false); - } - - private void persistState(final boolean flush) { - if (flush && persistTask != null) { - persistTask.cancel(true); - persistTask = null; - } - - if (persistTask == null) { - persistTask = (new AsyncTask() { - protected Boolean doInBackground(Object... param) { - // get the first item in the queue - ReadableMap queuedState = null; - if (queue.size() > 0) { - synchronized (StatePersistorModule.this) { - queuedState = queue.remove(flush ? queue.size() - 1 : 0); - if (flush) { - // we only want the final state in this scenario - queue.clear(); - } - } - } - - if (queuedState != null) { - ReadableMap state = queuedState; //(ReadableMap) filterState(queuedState); - // convert to JSON object - - try { - JSONObject json = readableMapToJSON(state); - - // save the state file - // TODO: explore this option at a later date - throw new UnsupportedOperationException(); - } catch (JSONException ex) { - // normally shouldn't happen, but if it does, reinsert into the queue - if (queuedState != null) { - synchronized (StatePersistorModule.this) { - queue.add(0, queuedState); - } - } - return false; - } - } - - return false; - } - - public void onPostExecute(Boolean result) { - if (queue.size() > 0) { - persistState(); - } - - persistTask = null; - } - }); - persistTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - @ReactMethod - public void flush() { - persistState(true); - } - - private static JSONObject readableMapToJSON(ReadableMap readableMap) throws JSONException { - JSONObject json = new JSONObject(); - ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); - while (iterator.hasNextKey()) { - String key = iterator.nextKey(); - switch (readableMap.getType(key)) { - case Map: - json.put(key, readableMapToJSON(readableMap.getMap(key))); - break; - case Array: - json.put(key, readableArrayToJSON(readableMap.getArray(key))); - break; - case Boolean: - json.put(key, readableMap.getBoolean(key)); - break; - case Null: - json.put(key, JSONObject.NULL); - break; - case Number: - json.put(key, readableMap.getDouble(key)); - break; - case String: - json.put(key, readableMap.getString(key)); - break; - } - } - - return json; - } - - private static JSONArray readableArrayToJSON(ReadableArray readableArray) throws JSONException { - JSONArray array = new JSONArray(); - for (int i = 0; i < readableArray.size(); i++) { - switch (readableArray.getType(i)) { - case Null: - break; - case Boolean: - array.put(readableArray.getBoolean(i)); - break; - case Number: - array.put(readableArray.getDouble(i)); - break; - case String: - array.put(readableArray.getString(i)); - break; - case Map: - array.put(readableMapToJSON(readableArray.getMap(i))); - break; - case Array: - array.put(readableArrayToJSON(readableArray.getArray(i))); - break; - } - } - - return array; - } -} \ No newline at end of file diff --git a/app/src/main/java/io/lbry/browser/reactmodules/UtilityModule.java b/app/src/main/java/io/lbry/browser/reactmodules/UtilityModule.java deleted file mode 100644 index 2be60d7e..00000000 --- a/app/src/main/java/io/lbry/browser/reactmodules/UtilityModule.java +++ /dev/null @@ -1,537 +0,0 @@ -package io.lbry.browser.reactmodules; - -import android.app.Activity; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.Bitmap; -import android.Manifest; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import androidx.core.content.FileProvider; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.ContextCompat; -import android.telephony.TelephonyManager; -import android.view.View; -import android.view.WindowManager; - -import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.common.MapBuilder; - -import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.squareup.picasso.Picasso; - -import java.io.File; -import java.io.Closeable; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; -import java.security.KeyStore; - -import io.lbry.browser.DownloadManager; -import io.lbry.browser.MainActivity; -import io.lbry.browser.R; -import io.lbry.lbrysdk.LbrynetService; -import io.lbry.lbrysdk.Utils; - -public class UtilityModule extends ReactContextBaseJavaModule { - private static final Map activeNotifications = new HashMap(); - - private static final String FILE_PROVIDER = "io.lbry.browser.fileprovider"; - - private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.SUBSCRIPTIONS_NOTIFICATION_CHANNEL"; - - public static final String ACTION_NOTIFICATION_PLAY = "io.lbry.browser.ACTION_NOTIFICATION_PLAY"; - - public static final String ACTION_NOTIFICATION_LATER = "io.lbry.browser.ACTION_NOTIFICATION_LATER"; - - // Setting keys from React Native - public static final String RECEIVE_SUBSCRIPTION_NOTIFICATIONS = "receiveSubscriptionNotifications"; - - public static final String RECEIVE_REWARD_NOTIFICATIONS = "receiveRewardNotifications"; - - public static final String RECEIVE_INTERESTS_NOTIFICATIONS = "receiveInterestsNotifications"; - - public static final String RECEIVE_CREATOR_NOTIFICATIONS = "receiveCreatorNotifications"; - - public static final String DHT_ENABLED = "dhtEnabled"; - - // the last language set to be loaded - private static final String LANGUAGE_SETTING_KEY = "language"; - - private String language; - - private Context context; - - private KeyStore keyStore; - - public UtilityModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - try { - this.keyStore = Utils.initKeyStore(context); - } catch (Exception ex) { - // continue without keystore - } - - SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - language = sp.getString(LANGUAGE_SETTING_KEY, "en"); - } - - @Override - public Map getConstants() { - final Map constants = MapBuilder.newHashMap(); - constants.put("language", language); - constants.put("dhtEnabled", LbrynetService.isDHTEnabled()); - return constants; - } - - @Override - public String getName() { - return "UtilityModule"; - } - - @ReactMethod - public void keepAwakeOn() { - final Activity activity = getCurrentActivity(); - - if (activity != null) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - }); - } - } - - @ReactMethod - public void keepAwakeOff() { - final Activity activity = getCurrentActivity(); - - if (activity != null) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - }); - } - } - - @ReactMethod - public void hideNavigationBar() { - final Activity activity = MainActivity.getActivity(); - if (activity != null) { - activity.runOnUiThread(new Runnable() { - public void run() { - View decorView = activity.getWindow().getDecorView(); - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - } - }); - } - } - - @ReactMethod - public void showNavigationBar() { - final Activity activity = MainActivity.getActivity(); - if (activity != null) { - activity.runOnUiThread(new Runnable() { - public void run() { - View decorView = activity.getWindow().getDecorView(); - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_VISIBLE); - } - }); - } - } - - @ReactMethod - public void getDeviceId(boolean requestPermission, final Promise promise) { - if (isEmulator()) { - promise.reject("Rewards cannot be claimed from an emulator nor virtual device."); - return; - } - - TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - String id = null; - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - id = telephonyManager.getImei(); // GSM - if (id == null) { - id = telephonyManager.getMeid(); // CDMA - } - } else { - id = telephonyManager.getDeviceId(); - } - } catch (SecurityException ex) { - // Maybe the permission was not granted? Try to acquire permission - /*if (requestPermission) { - requestPhoneStatePermission(); - }*/ - } catch (Exception ex) { - // id could not be obtained. Display a warning that rewards cannot be claimed. - promise.reject(ex.getMessage()); - } - - if (id == null || id.trim().length() == 0) { - promise.reject("Rewards cannot be claimed because your device could not be identified."); - return; - } - - promise.resolve(id); - } - - @ReactMethod - public void canReceiveSms(final Promise promise) { - promise.resolve(MainActivity.hasPermission(Manifest.permission.RECEIVE_SMS, MainActivity.getActivity())); - } - - @ReactMethod - public void requestReceiveSmsPermission() { - MainActivity activity = (MainActivity) MainActivity.getActivity(); - if (activity != null) { - // Request for the RECEIVE_SMS permission - MainActivity.checkReceiveSmsPermission(activity); - } - } - - @ReactMethod - public void canReadWriteStorage(final Promise promise) { - promise.resolve(MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, MainActivity.getActivity())); - } - - @ReactMethod - public void requestStoragePermission() { - MainActivity activity = (MainActivity) MainActivity.getActivity(); - if (activity != null) { - MainActivity.checkStoragePermission(activity); - } - } - - @ReactMethod - public void shareLogFile(Callback errorCallback) { - String logFileName = "lbrynet.log"; - File logFile = new File(String.format("%s/%s", Utils.getAppInternalStorageDir(context), "lbrynet"), logFileName); - if (!logFile.exists()) { - errorCallback.invoke("The lbrynet.log file could not be found."); - return; - } - - try { - Uri fileUri = FileProvider.getUriForFile(context, FILE_PROVIDER, logFile); - if (fileUri != null) { - Intent shareIntent = new Intent(); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.setType("text/plain"); - shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri); - - Intent sendLogIntent = Intent.createChooser(shareIntent, "Send LBRY log"); - sendLogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(sendLogIntent); - } - } catch (IllegalArgumentException e) { - errorCallback.invoke("The lbrynet.log file cannot be shared due to permission restrictions."); - } - } - - @ReactMethod - public void shareUrl(String url) { - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.setType("text/plain"); - shareIntent.putExtra(Intent.EXTRA_TEXT, url); - - Intent shareUrlIntent = Intent.createChooser(shareIntent, "Share LBRY content"); - shareUrlIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(shareUrlIntent); - } - - @ReactMethod - public void showNotificationForContent(final String uri, String title, String publisher, final String thumbnail, boolean isPlayable) { - final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = new NotificationChannel( - NOTIFICATION_CHANNEL_ID, "LBRY Subscriptions", NotificationManager.IMPORTANCE_DEFAULT); - channel.setDescription("LBRY subscription notifications"); - notificationManager.createNotificationChannel(channel); - } - - if (activeNotifications.containsKey(uri)) { - // the notification for the specified uri is already present, don't try to create another one - return; - } - - int id = 0; - Random random = new Random(); - do { - id = random.nextInt(); - } while (id < 100); - final int notificationId = id; - - String uriWithParam = String.format("%s?download=true", uri); - Intent playIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uriWithParam)); - playIntent.putExtra(MainActivity.SOURCE_NOTIFICATION_ID_KEY, notificationId); - playIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - PendingIntent playPendingIntent = PendingIntent.getActivity(context, 0, playIntent, PendingIntent.FLAG_CANCEL_CURRENT); - - boolean hasThumbnail = false; - final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID); - builder.setAutoCancel(true) - .setColor(ContextCompat.getColor(context, R.color.lbryGreen)) - .setContentIntent(DownloadManager.getLaunchPendingIntent(uri, context)) - .setContentTitle(publisher) - .setContentText(title) - .setSmallIcon(R.drawable.ic_lbry) - .addAction(android.R.drawable.ic_media_play, (isPlayable ? "Play" : "Open"), playPendingIntent); - - activeNotifications.put(uri, notificationId); - if (thumbnail != null) { - // attempt to load the thumbnail Bitmap before displaying the notification - final Uri thumbnailUri = Uri.parse(thumbnail); - if (thumbnailUri != null) { - hasThumbnail = true; - (new AsyncTask() { - protected Bitmap doInBackground(Void... params) { - try { - return Picasso.get().load(thumbnailUri).get(); - } catch (Exception e) { - return null; - } - } - - protected void onPostExecute(Bitmap result) { - if (result != null) { - builder.setLargeIcon(result) - .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(result).bigLargeIcon(null)); - } - notificationManager.notify(notificationId, builder.build()); - } - }).execute(); - } - } - - if (!hasThumbnail) { - notificationManager.notify(notificationId, builder.build()); - } - } - - private static boolean isEmulator() { - String buildModel = Build.MODEL.toLowerCase(); - return (// Check FINGERPRINT - Build.FINGERPRINT.startsWith("generic") || - Build.FINGERPRINT.startsWith("unknown") || - Build.FINGERPRINT.contains("test-keys") || - - // Check MODEL - buildModel.contains("google_sdk") || - buildModel.contains("emulator") || - buildModel.contains("android sdk built for x86") || - - // Check MANUFACTURER - Build.MANUFACTURER.contains("Genymotion") || - "unknown".equals(Build.MANUFACTURER) || - - // Check HARDWARE - Build.HARDWARE.contains("goldfish") || - Build.HARDWARE.contains("vbox86") || - - // Check PRODUCT - "google_sdk".equals(Build.PRODUCT) || - "sdk_google_phone_x86".equals(Build.PRODUCT) || - "sdk".equals(Build.PRODUCT) || - "sdk_x86".equals(Build.PRODUCT) || - "vbox86p".equals(Build.PRODUCT) || - - // Check BRAND and DEVICE - (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) - ); - } - - @ReactMethod - public void setSecureValue(String key, String value) { - if (keyStore != null) { - Utils.setSecureValue(key, value, context, keyStore); - } - } - - @ReactMethod - public void getSecureValue(String key, Promise promise) { - if (keyStore == null) { - promise.reject("no keyStore found"); - return; - } - - promise.resolve(Utils.getSecureValue(key, context, keyStore)); - } - - @ReactMethod - public void checkDownloads() { - Intent intent = new Intent(); - intent.setAction(LbrynetService.ACTION_CHECK_DOWNLOADS); - if (context != null) { - context.sendBroadcast(intent); - } - } - - @ReactMethod - public void queueDownload(String outpoint) { - Intent intent = new Intent(); - intent.setAction(LbrynetService.ACTION_QUEUE_DOWNLOAD); - intent.putExtra("outpoint", outpoint); - - if (context != null) { - context.sendBroadcast(intent); - } - } - - @ReactMethod - public void deleteDownload(String uri) { - Intent intent = new Intent(); - intent.setAction(LbrynetService.ACTION_DELETE_DOWNLOAD); - intent.putExtra("uri", uri); - if (context != null) { - context.sendBroadcast(intent); - } - } - - @ReactMethod - public void openDocumentPicker(String type) { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.setType(type); - Activity activity = MainActivity.getActivity(); - if (activity != null) { - activity.startActivityForResult( - Intent.createChooser(intent, "Select a file"), MainActivity.DOCUMENT_PICKER_RESULT_CODE); - } - } - - @ReactMethod - public void setNativeBooleanSetting(String key, final boolean value) { - if (context != null) { - SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sp.edit(); - editor.putBoolean(key, value); - editor.commit(); - } - - if (DHT_ENABLED.equalsIgnoreCase(key)) { - (new AsyncTask() { - protected Void doInBackground(Void... params) { - String fileContent = value ? "on" : "off"; - String path = String.format("%s/%s", Utils.getAppInternalStorageDir(context), "dht"); - PrintStream out = null; - try { - out = new PrintStream(new FileOutputStream(path)); - out.print(fileContent); - } catch (Exception ex) { - // pass - } finally { - if (out != null) { - out.close(); - } - } - return null; - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - @ReactMethod - public void getNativeBooleanSetting(String key, boolean defaultValue, Promise promise) { - if (context != null) { - SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - promise.resolve(sp.getBoolean(key, defaultValue)); - } else { - promise.resolve(null); - } - } - - @ReactMethod - public void setNativeStringSetting(String key, String value) { - if (context != null) { - SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sp.edit(); - editor.putString(key, value); - editor.commit(); - } - } - - @ReactMethod - public void getNativeStringSetting(String key, String defaultValue, Promise promise) { - if (context != null) { - SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - promise.resolve(sp.getString(key, defaultValue)); - } else { - promise.resolve(null); - } - } - - @ReactMethod - public void getNotificationLaunchTarget(Promise promise) { - Activity activity = MainActivity.getActivity(); - if (activity != null) { - Intent intent = activity.getIntent(); - if (intent != null) { - String target = intent.getStringExtra("target"); - if (target != null && target.trim().length() > 0) { - promise.resolve(target); - return; - } - } - } - - promise.resolve(null); - } - - @ReactMethod - public void getDownloadDirectory(Promise promise) { - // This obtains a public default download directory after the storage permission has been granted - promise.resolve(Utils.getConfiguredDownloadDirectory(context)); - } - - @ReactMethod - public void getLbrynetDirectory(Promise promise) { - String path = String.format("%s/%s", Utils.getAppInternalStorageDir(context), "lbrynet"); - promise.resolve(path); - } - - @ReactMethod - public void getPlatform(Promise promise) { - String platform = String.format("Android %s (API %s)", Utils.getAndroidRelease(), Utils.getAndroidSdk()); - promise.resolve(platform); - } - - @ReactMethod - public void checkSdkReady() { - // check that the sdk ready when the service is already running so that we can send the ready event - ReactContext reactContext = (ReactContext) context; - if (MainActivity.lbrySdkReady && reactContext != null) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onSdkReady", null); - } - } - - @ReactMethod - public void log(String tag, String message) { - android.util.Log.d(tag, message); - } -} diff --git a/app/src/main/java/io/lbry/browser/reactmodules/VersionInfoModule.java b/app/src/main/java/io/lbry/browser/reactmodules/VersionInfoModule.java deleted file mode 100644 index 387f1d9c..00000000 --- a/app/src/main/java/io/lbry/browser/reactmodules/VersionInfoModule.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.lbry.browser.reactmodules; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; - -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; - -public class VersionInfoModule extends ReactContextBaseJavaModule { - private Context context; - - public VersionInfoModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - } - - @Override - public String getName() { - return "VersionInfo"; - } - - @ReactMethod - public void getAppVersion(final Promise promise) { - PackageManager packageManager = this.context.getPackageManager(); - String packageName = this.context.getPackageName(); - try { - PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0); - promise.resolve(packageInfo.versionName); - } catch (PackageManager.NameNotFoundException e) { - // normally shouldn't happen - promise.resolve("Unknown"); - } - } -} diff --git a/app/src/main/java/io/lbry/browser/reactpackages/LbryReactPackage.java b/app/src/main/java/io/lbry/browser/reactpackages/LbryReactPackage.java deleted file mode 100644 index 7b1f53e5..00000000 --- a/app/src/main/java/io/lbry/browser/reactpackages/LbryReactPackage.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.lbry.browser.reactpackages; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; - -import io.lbry.browser.reactmodules.BackgroundMediaModule; -import io.lbry.browser.reactmodules.DaemonServiceControlModule; -import io.lbry.browser.reactmodules.FirstRunModule; -import io.lbry.browser.reactmodules.FirebaseModule; -import io.lbry.browser.reactmodules.GalleryModule; -import io.lbry.browser.reactmodules.RequestsModule; -import io.lbry.browser.reactmodules.ScreenOrientationModule; -import io.lbry.browser.reactmodules.StatePersistorModule; -import io.lbry.browser.reactmodules.VersionInfoModule; -import io.lbry.browser.reactmodules.UtilityModule; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class LbryReactPackage implements ReactPackage { - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.emptyList(); - } - - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - List modules = new ArrayList<>(); - - modules.add(new BackgroundMediaModule(reactContext)); - modules.add(new DaemonServiceControlModule(reactContext)); - modules.add(new FirstRunModule(reactContext)); - modules.add(new FirebaseModule(reactContext)); - modules.add(new GalleryModule(reactContext)); - modules.add(new RequestsModule(reactContext)); - modules.add(new ScreenOrientationModule(reactContext)); - modules.add(new StatePersistorModule(reactContext)); - modules.add(new UtilityModule(reactContext)); - modules.add(new VersionInfoModule(reactContext)); - - return modules; - } -} diff --git a/app/src/main/java/io/lbry/browser/receivers/NotificationDeletedReceiver.java b/app/src/main/java/io/lbry/browser/receivers/NotificationDeletedReceiver.java deleted file mode 100644 index b3d02f22..00000000 --- a/app/src/main/java/io/lbry/browser/receivers/NotificationDeletedReceiver.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.lbry.browser.receivers; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import io.lbry.browser.DownloadManager; - -public class NotificationDeletedReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - int notificationId = intent.getExtras().getInt(DownloadManager.NOTIFICATION_ID_KEY); - if (DownloadManager.DOWNLOAD_NOTIFICATION_GROUP_ID == notificationId) { - DownloadManager.groupCreated = false; - } - } -} diff --git a/app/src/main/java/io/lbry/browser/tasks/FollowUnfollowTagTask.java b/app/src/main/java/io/lbry/browser/tasks/FollowUnfollowTagTask.java new file mode 100644 index 00000000..3c7f4dca --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/FollowUnfollowTagTask.java @@ -0,0 +1,65 @@ +package io.lbry.browser.tasks; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.model.Tag; +import io.lbry.browser.utils.Lbry; + +public class FollowUnfollowTagTask extends AsyncTask { + private Tag tag; + private boolean unfollowing; + private Context context; + private FollowUnfollowTagHandler handler; + private Exception error; + + public FollowUnfollowTagTask(Tag tag, boolean unfollowing, Context context, FollowUnfollowTagHandler handler) { + this.tag = tag; + this.context = context; + this.unfollowing = unfollowing; + this.handler = handler; + } + public Boolean doInBackground(Void... params) { + try { + SQLiteDatabase db = null; + if (context instanceof MainActivity) { + db = ((MainActivity) context).getDbHelper().getWritableDatabase(); + if (db != null) { + if (!Lbry.knownTags.contains(tag)) { + DatabaseHelper.createOrUpdateTag(tag, db); + Lbry.addKnownTag(tag); + } + + tag.setFollowed(!unfollowing); + DatabaseHelper.createOrUpdateTag(tag, db); + if (unfollowing) { + Lbry.removeFollowedTag(tag); + } else { + Lbry.addFollowedTag(tag); + } + return true; + } + } + } catch (Exception ex) { + error = ex; + } + return false; + } + protected void onPostExecute(Boolean result) { + if (handler != null) { + if (result) { + handler.onSuccess(tag, unfollowing); + } else { + handler.onError(error); + } + } + } + + public interface FollowUnfollowTagHandler { + void onSuccess(Tag tag, boolean unfollowing); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/GenericTaskHandler.java b/app/src/main/java/io/lbry/browser/tasks/GenericTaskHandler.java new file mode 100644 index 00000000..19c10d10 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/GenericTaskHandler.java @@ -0,0 +1,7 @@ +package io.lbry.browser.tasks; + +public interface GenericTaskHandler { + void beforeStart(); + void onSuccess(); + void onError(Exception error); +} diff --git a/app/src/main/java/io/lbry/browser/tasks/LighthouseAutoCompleteTask.java b/app/src/main/java/io/lbry/browser/tasks/LighthouseAutoCompleteTask.java new file mode 100644 index 00000000..a0c32b86 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/LighthouseAutoCompleteTask.java @@ -0,0 +1,51 @@ +package io.lbry.browser.tasks; + +import android.os.AsyncTask; +import android.view.View; + +import java.util.List; + +import io.lbry.browser.exceptions.LbryRequestException; +import io.lbry.browser.exceptions.LbryResponseException; +import io.lbry.browser.model.UrlSuggestion; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lighthouse; + +public class LighthouseAutoCompleteTask extends AsyncTask> { + private String text; + private AutoCompleteResultHandler handler; + private View progressView; + private Exception error; + + public LighthouseAutoCompleteTask(String text, View progressView, AutoCompleteResultHandler handler) { + this.text = text; + this.progressView = progressView; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected List doInBackground(Void... params) { + try { + return Lighthouse.autocomplete(text); + } catch (LbryRequestException | LbryResponseException ex) { + error = ex; + return null; + } + } + protected void onPostExecute(List suggestions) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (suggestions != null) { + handler.onSuccess(suggestions); + } else { + handler.onError(error); + } + } + } + + public interface AutoCompleteResultHandler { + void onSuccess(List suggestions); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/LighthouseSearchTask.java b/app/src/main/java/io/lbry/browser/tasks/LighthouseSearchTask.java new file mode 100644 index 00000000..6da1d943 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/LighthouseSearchTask.java @@ -0,0 +1,56 @@ +package io.lbry.browser.tasks; + +import android.os.AsyncTask; +import android.view.View; +import android.widget.ProgressBar; + +import java.util.List; + +import io.lbry.browser.exceptions.LbryRequestException; +import io.lbry.browser.exceptions.LbryResponseException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.tasks.claim.ClaimSearchResultHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lighthouse; + +public class LighthouseSearchTask extends AsyncTask> { + private String rawQuery; + private int size; + private int from; + private boolean nsfw; + private String relatedTo; + private ClaimSearchResultHandler handler; + private ProgressBar progressBar; + private Exception error; + + public LighthouseSearchTask(String rawQuery, int size, int from, boolean nsfw, String relatedTo, ProgressBar progressBar, ClaimSearchResultHandler handler) { + this.rawQuery = rawQuery; + this.size = size; + this.from = from; + this.nsfw = nsfw; + this.relatedTo = relatedTo; + this.progressBar = progressBar; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressBar, View.VISIBLE); + } + protected List doInBackground(Void... params) { + try { + return Lighthouse.search(rawQuery, size, from, nsfw, relatedTo); + } catch (LbryRequestException | LbryResponseException ex) { + error = ex; + return null; + } + } + protected void onPostExecute(List claims) { + Helper.setViewVisibility(progressBar, View.GONE); + if (handler != null) { + if (claims != null) { + handler.onSuccess(claims, claims.size() < size); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/LoadTagsTask.java b/app/src/main/java/io/lbry/browser/tasks/LoadTagsTask.java new file mode 100644 index 00000000..4eb465e3 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/LoadTagsTask.java @@ -0,0 +1,59 @@ +package io.lbry.browser.tasks; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.os.AsyncTask; +import android.view.View; +import android.widget.ProgressBar; + +import java.util.List; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.exceptions.LbryRequestException; +import io.lbry.browser.exceptions.LbryResponseException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.Tag; +import io.lbry.browser.utils.Helper; + +public class LoadTagsTask extends AsyncTask> { + private Context context; + private LoadTagsHandler handler; + private Exception error; + + public LoadTagsTask(Context context, LoadTagsHandler handler) { + this.context = context; + this.handler = handler; + } + protected List doInBackground(Void... params) { + List tags = null; + SQLiteDatabase db = null; + try { + if (context instanceof MainActivity) { + db = ((MainActivity) context).getDbHelper().getReadableDatabase(); + if (db != null) { + tags = DatabaseHelper.getTags(db); + } + } + } catch (SQLiteException ex) { + error = ex; + } + + return tags; + } + protected void onPostExecute(List tags) { + if (handler != null) { + if (tags != null) { + handler.onSuccess(tags); + } else { + handler.onError(error); + } + } + } + + public interface LoadTagsHandler { + void onSuccess(List tags); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/MergeSubscriptionsTask.java b/app/src/main/java/io/lbry/browser/tasks/MergeSubscriptionsTask.java new file mode 100644 index 00000000..892ad6ba --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/MergeSubscriptionsTask.java @@ -0,0 +1,133 @@ +package io.lbry.browser.tasks; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.os.AsyncTask; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; +import okhttp3.Response; + +// background task to create a diff of local and remote subscriptions and try to merge +public class MergeSubscriptionsTask extends AsyncTask> { + private static final String TAG = "MergeSubscriptionsTask"; + private Context context; + private List base; + private List diff; + private MergeSubscriptionsHandler handler; + private Exception error; + + public MergeSubscriptionsTask(List base, Context context, MergeSubscriptionsHandler handler) { + this.base = base; + this.context = context; + this.handler = handler; + } + + protected List doInBackground(Void... params) { + List combined = new ArrayList<>(base); + List localSubs = new ArrayList<>(); + List remoteSubs = new ArrayList<>(); + diff = new ArrayList<>(); + SQLiteDatabase db = null; + try { + // fetch local subscriptions + if (context instanceof MainActivity) { + db = ((MainActivity) context).getDbHelper().getWritableDatabase(); + } + if (db != null) { + localSubs = DatabaseHelper.getSubscriptions(db); + for (Subscription sub : localSubs) { + if (!combined.contains(sub)) { + combined.add(sub); + } + } + } + + // fetch remote subscriptions + JSONArray array = (JSONArray) Lbryio.parseResponse(Lbryio.call("subscription", "list", context)); + if (array != null) { + for (int i = 0; i < array.length(); i++) { + JSONObject item = array.getJSONObject(i); + String claimId = item.getString("claim_id"); + String channelName = item.getString("channel_name"); + + LbryUri url = new LbryUri(); + url.setChannelName(channelName); + url.setClaimId(claimId); + Subscription subscription = new Subscription(channelName, url.toString()); + remoteSubs.add(subscription); + } + } + + for (int i = 0; i < combined.size(); i++) { + Subscription local = combined.get(i); + if (!remoteSubs.contains(local)) { + // add to remote subscriptions + try { + LbryUri uri = LbryUri.parse(local.getUrl()); + Map options = new HashMap<>(); + options.put("claim_id", uri.getChannelClaimId()); + options.put("channel_name", Helper.normalizeChannelName(local.getChannelName())); + Lbryio.parseResponse(Lbryio.call("subscription", "new", options, context)); + } catch (LbryUriException | LbryioRequestException | LbryioResponseException ex) { + // pass + Log.e(TAG, String.format("subscription/new failed: %s", ex.getMessage()), ex); + } + } + } + + for (int i = 0; i < localSubs.size(); i++) { + Subscription local = localSubs.get(i); + if (!base.contains(local) && !diff.contains(local)) { + diff.add(local); + } + } + for (int i = 0; i < remoteSubs.size(); i++) { + Subscription remote = remoteSubs.get(i); + if (!combined.contains(remote)) { + combined.add(remote); + if (!diff.contains(remote)) { + diff.add(remote); + } + } + } + } catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException | IllegalStateException | SQLiteException ex) { + error = ex; + return null; + } + + return combined; + } + protected void onPostExecute(List subscriptions) { + if (handler != null) { + if (subscriptions != null) { + handler.onSuccess(subscriptions, diff); + } else { + handler.onError(error); + } + } + } + + public interface MergeSubscriptionsHandler { + void onSuccess(List subscriptions, List diff); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/ReadTextFileTask.java b/app/src/main/java/io/lbry/browser/tasks/ReadTextFileTask.java new file mode 100644 index 00000000..3dfa6b93 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/ReadTextFileTask.java @@ -0,0 +1,55 @@ +package io.lbry.browser.tasks; + +import android.os.AsyncTask; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.Buffer; + +import io.lbry.browser.utils.Helper; + +public class ReadTextFileTask extends AsyncTask { + private String filePath; + private Exception error; + private ReadTextFileHandler handler; + public ReadTextFileTask(String filePath, ReadTextFileHandler handler) { + this.filePath = filePath; + this.handler = handler; + } + protected String doInBackground(Void... params) { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath))); + String line = null; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + } catch (IOException ex) { + error = ex; + return null; + } finally { + Helper.closeCloseable(reader); + } + + return sb.toString(); + } + protected void onPostExecute(String text) { + if (handler != null) { + if (!Helper.isNull(text)) { + handler.onSuccess(text); + } else { + handler.onError(error); + } + } + } + + public interface ReadTextFileHandler { + void onSuccess(String text); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/SetSdkSettingTask.java b/app/src/main/java/io/lbry/browser/tasks/SetSdkSettingTask.java new file mode 100644 index 00000000..42dafdc7 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/SetSdkSettingTask.java @@ -0,0 +1,45 @@ +package io.lbry.browser.tasks; + +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.utils.Lbry; + +public class SetSdkSettingTask extends AsyncTask { + private String key; + private String value; + private GenericTaskHandler handler; + private Exception error; + public SetSdkSettingTask(String key, String value, GenericTaskHandler handler) { + this.key = key; + this.value = value; + this.handler = handler; + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("key", key); + options.put("value", value); + Lbry.genericApiCall("setting_set", options); + return true; + } catch (ApiCallException ex) { + error = ex; + return false; + } + } + + protected void onPostExecute(Boolean result) { + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/UpdateSuggestedTagsTask.java b/app/src/main/java/io/lbry/browser/tasks/UpdateSuggestedTagsTask.java new file mode 100644 index 00000000..aee8dc1a --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/UpdateSuggestedTagsTask.java @@ -0,0 +1,85 @@ +package io.lbry.browser.tasks; + +import android.os.AsyncTask; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import io.lbry.browser.adapter.TagListAdapter; +import io.lbry.browser.model.Tag; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class UpdateSuggestedTagsTask extends AsyncTask> { + + private boolean clearPrevious; + private boolean excludeMature; + private int limit; + private String filter; + private TagListAdapter addedTagsAdapter; + private TagListAdapter suggestedTagsAdapter; + private KnownTagsHandler handler; + + public UpdateSuggestedTagsTask( + String filter, + int limit, + TagListAdapter addedTagsAdapter, + TagListAdapter suggestedTagsAdapter, + boolean clearPrevious, + boolean excludeMature, + KnownTagsHandler handler) { + this.filter = filter; + this.limit = limit; + this.addedTagsAdapter = addedTagsAdapter; + this.suggestedTagsAdapter = suggestedTagsAdapter; + this.clearPrevious = clearPrevious; + this.excludeMature = excludeMature; + this.handler = handler; + } + + protected List doInBackground(Void... params) { + List tags = new ArrayList<>(); + if (Helper.isNullOrEmpty(filter)) { + Random random = new Random(); + if (suggestedTagsAdapter != null && !clearPrevious) { + tags = new ArrayList<>(suggestedTagsAdapter.getTags()); + } + while (tags.size() < limit) { + Tag randomTag = Lbry.knownTags.get(random.nextInt(Lbry.knownTags.size())); + if (excludeMature && randomTag.isMature()) { + continue; + } + if (!Lbry.followedTags.contains(randomTag) && (addedTagsAdapter == null || !addedTagsAdapter.getTags().contains(randomTag))) { + tags.add(randomTag); + } + } + } else { + Tag filterTag = new Tag(filter); + if (addedTagsAdapter == null || !addedTagsAdapter.getTags().contains(filterTag)) { + tags.add(new Tag(filter)); + } + for (int i = 0; i < Lbry.knownTags.size() && tags.size() < limit - 1; i++) { + Tag knownTag = Lbry.knownTags.get(i); + if (excludeMature && knownTag.isMature()) { + continue; + } + if ((knownTag.getLowercaseName().startsWith(filter) || knownTag.getLowercaseName().matches(filter)) && + (!tags.contains(knownTag) && + !Lbry.followedTags.contains(knownTag) && (addedTagsAdapter == null || !addedTagsAdapter.getTags().contains(knownTag)))) { + tags.add(knownTag); + } + } + } + return tags; + } + protected void onPostExecute(List tags) { + if (handler != null) { + handler.onSuccess(tags); + } + } + + public interface KnownTagsHandler { + void onSuccess(List tags); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/UploadImageTask.java b/app/src/main/java/io/lbry/browser/tasks/UploadImageTask.java new file mode 100644 index 00000000..2dcd8d65 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/UploadImageTask.java @@ -0,0 +1,100 @@ +package io.lbry.browser.tasks; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +import java.io.File; +import java.util.concurrent.TimeUnit; + +import io.lbry.browser.exceptions.LbryResponseException; +import io.lbry.browser.utils.Helper; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class UploadImageTask extends AsyncTask { + private String filePath; + private View progressView; + private UploadThumbnailHandler handler; + private Exception error; + + public UploadImageTask(String filePath, View progressView, UploadThumbnailHandler handler) { + this.filePath = filePath; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected String doInBackground(Void... params) { + String thumbnailUrl = null; + try { + File file = new File(filePath); + String fileName = file.getName(); + int dotIndex = fileName.lastIndexOf('.'); + String extension = "jpg"; + if (dotIndex > -1) { + extension = fileName.substring(dotIndex + 1); + } + String fileType = String.format("image/%s", extension); + RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM). + addFormDataPart("name", Helper.makeid(24)). + addFormDataPart("file", fileName, RequestBody.create(file, MediaType.parse(fileType))). + build(); + Request request = new Request.Builder().url("https://spee.ch/api/claim/publish").post(body).build(); + OkHttpClient client = new OkHttpClient.Builder(). + writeTimeout(300, TimeUnit.SECONDS). + readTimeout(300, TimeUnit.SECONDS). + build(); + Response response = client.newCall(request).execute(); + JSONObject json = new JSONObject(response.body().string()); + if (json.has("success") && Helper.getJSONBoolean("success", false, json)) { + JSONObject data = json.getJSONObject("data"); + String url = Helper.getJSONString("url", null, data); + if (Helper.isNullOrEmpty(url)) { + throw new LbryResponseException("Invalid thumbnail url returned after upload."); + } + + thumbnailUrl = String.format("%s.%s", url, extension); + } else if (json.has("error") || json.has("message")) { + JSONObject error = Helper.getJSONObject("error", json); + String message = null; + if (error != null) { + message = Helper.getJSONString("message", null, error); + } + if (Helper.isNullOrEmpty(message)) { + message = Helper.getJSONString("message", null, json); + } + throw new LbryResponseException(Helper.isNullOrEmpty(message) ? "The image failed to upload." : message); + } + } catch (IOException | JSONException | LbryResponseException ex) { + error = ex; + } + + return thumbnailUrl; + } + protected void onPostExecute(String thumbnailUrl) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (!Helper.isNullOrEmpty(thumbnailUrl)) { + handler.onSuccess(thumbnailUrl); + } else { + handler.onError(error); + } + } + } + + public interface UploadThumbnailHandler { + void onSuccess(String url); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/AbandonChannelTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/AbandonChannelTask.java new file mode 100644 index 00000000..3b71a45b --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/AbandonChannelTask.java @@ -0,0 +1,63 @@ +package io.lbry.browser.tasks.claim; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class AbandonChannelTask extends AsyncTask { + private List claimIds; + private List successfulClaimIds; + private List failedClaimIds; + private List failedExceptions; + private View progressView; + private AbandonHandler handler; + + public AbandonChannelTask(List claimIds, View progressView, AbandonHandler handler) { + this.claimIds = claimIds; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + public Boolean doInBackground(Void... params) { + successfulClaimIds = new ArrayList<>(); + failedClaimIds = new ArrayList<>(); + failedExceptions = new ArrayList<>(); + + for (String claimId : claimIds) { + try { + Map options = new HashMap<>(); + options.put("claim_id", claimId); + options.put("blocking", true); + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_CHANNEL_ABANDON, options); + successfulClaimIds.add(claimId); + } catch (ApiCallException ex) { + failedClaimIds.add(claimId); + failedExceptions.add(ex); + } + } + + return true; + } + + protected void onPostExecute(Boolean result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + handler.onComplete(successfulClaimIds, failedClaimIds, failedExceptions); + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/AbandonHandler.java b/app/src/main/java/io/lbry/browser/tasks/claim/AbandonHandler.java new file mode 100644 index 00000000..706eaeb4 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/AbandonHandler.java @@ -0,0 +1,7 @@ +package io.lbry.browser.tasks.claim; + +import java.util.List; + +public interface AbandonHandler { + void onComplete(List successfulClaimIds, List failedClaimIds, List errors); +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/AbandonStreamTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/AbandonStreamTask.java new file mode 100644 index 00000000..a5c68d93 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/AbandonStreamTask.java @@ -0,0 +1,63 @@ +package io.lbry.browser.tasks.claim; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class AbandonStreamTask extends AsyncTask { + private List claimIds; + private List successfulClaimIds; + private List failedClaimIds; + private List failedExceptions; + private View progressView; + private AbandonHandler handler; + + public AbandonStreamTask(List claimIds, View progressView, AbandonHandler handler) { + this.claimIds = claimIds; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + public Boolean doInBackground(Void... params) { + successfulClaimIds = new ArrayList<>(); + failedClaimIds = new ArrayList<>(); + failedExceptions = new ArrayList<>(); + + for (String claimId : claimIds) { + try { + Map options = new HashMap<>(); + options.put("claim_id", claimId); + options.put("blocking", true); + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_STREAM_ABANDON, options); + successfulClaimIds.add(claimId); + } catch (ApiCallException ex) { + failedClaimIds.add(claimId); + failedExceptions.add(ex); + } + } + + return true; + } + + protected void onPostExecute(Boolean result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + handler.onComplete(successfulClaimIds, failedClaimIds, failedExceptions); + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/ChannelCreateUpdateTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/ChannelCreateUpdateTask.java new file mode 100644 index 00000000..6611572d --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ChannelCreateUpdateTask.java @@ -0,0 +1,107 @@ +package io.lbry.browser.tasks.claim; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.tasks.claim.ClaimResultHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class ChannelCreateUpdateTask extends AsyncTask { + private Claim claim; + private BigDecimal deposit; + private boolean update; + private Exception error; + private ClaimResultHandler handler; + private View progressView; + + public ChannelCreateUpdateTask(Claim claim, BigDecimal deposit, boolean update, View progressView, ClaimResultHandler handler) { + this.claim = claim; + this.deposit = deposit; + this.update = update; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + if (handler != null) { + handler.beforeStart(); + } + } + protected Claim doInBackground(Void... params) { + Map options = new HashMap<>(); + if (!update) { + options.put("name", claim.getName()); + } else { + options.put("claim_id", claim.getClaimId()); + } + options.put("bid", new DecimalFormat(Helper.SDK_AMOUNT_FORMAT, new DecimalFormatSymbols(Locale.US)).format(deposit.doubleValue())); + if (!Helper.isNullOrEmpty(claim.getTitle())) { + options.put("title", claim.getTitle()); + } + if (!Helper.isNullOrEmpty(claim.getCoverUrl())) { + options.put("cover_url", claim.getCoverUrl()); + } + if (!Helper.isNullOrEmpty(claim.getThumbnailUrl())) { + options.put("thumbnail_url", claim.getThumbnailUrl()); + } + if (!Helper.isNullOrEmpty(claim.getDescription())) { + options.put("description", claim.getDescription()); + } + if (!Helper.isNullOrEmpty(claim.getWebsiteUrl())) { + options.put("website_url", claim.getWebsiteUrl()); + } + if (!Helper.isNullOrEmpty(claim.getEmail())) { + options.put("email", claim.getEmail()); + } + if (claim.getTags() != null && claim.getTags().size() > 0) { + options.put("tags", claim.getTags()); + } + options.put("blocking", true); + + Claim claimResult = null; + String method = !update ? Lbry.METHOD_CHANNEL_CREATE : Lbry.METHOD_CHANNEL_UPDATE; + try { + JSONObject result = (JSONObject) Lbry.genericApiCall(method, options); + if (result.has("outputs")) { + JSONArray outputs = result.getJSONArray("outputs"); + for (int i = 0; i < outputs.length(); i++) { + JSONObject output = outputs.getJSONObject(i); + if (output.has("claim_id") && output.has("claim_op")) { + claimResult = Claim.claimFromOutput(output); + break; + } + } + } + } catch (ApiCallException | ClassCastException | JSONException ex) { + error = ex; + } + + return claimResult; + } + + protected void onPostExecute(Claim result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result != null) { + handler.onSuccess(result); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/ClaimListResultHandler.java b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimListResultHandler.java new file mode 100644 index 00000000..2d427e88 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimListResultHandler.java @@ -0,0 +1,10 @@ +package io.lbry.browser.tasks.claim; + +import java.util.List; + +import io.lbry.browser.model.Claim; + +public interface ClaimListResultHandler { + void onSuccess(List claims); + void onError(Exception error); +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/ClaimListTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimListTask.java new file mode 100644 index 00000000..b017a0ba --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimListTask.java @@ -0,0 +1,71 @@ +package io.lbry.browser.tasks.claim; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class ClaimListTask extends AsyncTask> { + private List types; + private View progressView; + private ClaimListResultHandler handler; + private Exception error; + + public ClaimListTask(String type, View progressView, ClaimListResultHandler handler) { + this(Arrays.asList(type), progressView, handler); + } + public ClaimListTask(List types, View progressView, ClaimListResultHandler handler) { + this.types = types; + this.progressView = progressView; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected List doInBackground(Void... params) { + List claims = null; + + try { + Map options = new HashMap<>(); + if (types != null && types.size() > 0) { + options.put("claim_type", types); + } + options.put("page", 1); + options.put("page_size", 999); + options.put("resolve", true); + + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_CLAIM_LIST, options); + JSONArray items = result.getJSONArray("items"); + claims = new ArrayList<>(); + for (int i = 0; i < items.length(); i++) { + claims.add(Claim.fromJSONObject(items.getJSONObject(i))); + } + } catch (ApiCallException | JSONException ex) { + error = ex; + } + return claims; + } + protected void onPostExecute(List claims) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (claims != null) { + handler.onSuccess(claims); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/ClaimResultHandler.java b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimResultHandler.java new file mode 100644 index 00000000..5171486b --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimResultHandler.java @@ -0,0 +1,9 @@ +package io.lbry.browser.tasks.claim; + +import io.lbry.browser.model.Claim; + +public interface ClaimResultHandler { + void beforeStart(); + void onSuccess(Claim claimResult); + void onError(Exception error); +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/ClaimSearchResultHandler.java b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimSearchResultHandler.java new file mode 100644 index 00000000..2f4bdd8c --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimSearchResultHandler.java @@ -0,0 +1,10 @@ +package io.lbry.browser.tasks.claim; + +import java.util.List; + +import io.lbry.browser.model.Claim; + +public interface ClaimSearchResultHandler { + void onSuccess(List claims, boolean hasReachedEnd); + void onError(Exception error); +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/ClaimSearchTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimSearchTask.java new file mode 100644 index 00000000..696eb60c --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimSearchTask.java @@ -0,0 +1,49 @@ +package io.lbry.browser.tasks.claim; + +import android.os.AsyncTask; +import android.view.View; + +import java.util.List; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class ClaimSearchTask extends AsyncTask> { + private Map options; + private String connectionString; + private ClaimSearchResultHandler handler; + private View progressView; + private ApiCallException error; + + public ClaimSearchTask(Map options, String connectionString, View progressView, ClaimSearchResultHandler handler) { + this.options = options; + this.connectionString = connectionString; + this.progressView = progressView; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected List doInBackground(Void... params) { + try { + return Helper.filterInvalidReposts(Lbry.claimSearch(options, connectionString)); + } catch (ApiCallException ex) { + error = ex; + return null; + } + } + protected void onPostExecute(List claims) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (claims != null) { + handler.onSuccess(claims, claims.size() < Helper.parseInt(options.get("page_size"), 0)); + } else { + handler.onError(error); + } + } + } + +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/PublishClaimTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/PublishClaimTask.java new file mode 100644 index 00000000..418f114b --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/PublishClaimTask.java @@ -0,0 +1,106 @@ +package io.lbry.browser.tasks.claim; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class PublishClaimTask extends AsyncTask { + private Claim claim; + private String filePath; + private View progressView; + private ClaimResultHandler handler; + private Exception error; + public PublishClaimTask(Claim claim, String filePath, View progressView, ClaimResultHandler handler) { + this.claim = claim; + this.filePath = filePath; + this.progressView = progressView; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + if (handler != null) { + handler.beforeStart(); + } + } + protected Claim doInBackground(Void... params) { + Claim.StreamMetadata metadata = (Claim.StreamMetadata) claim.getValue(); + DecimalFormat amountFormat = new DecimalFormat(Helper.SDK_AMOUNT_FORMAT, new DecimalFormatSymbols(Locale.US)); + + Map options = new HashMap<>(); + options.put("blocking", true); + options.put("name", claim.getName()); + options.put("bid", amountFormat.format(new BigDecimal(claim.getAmount()).doubleValue())); + options.put("title", Helper.isNullOrEmpty(claim.getTitle()) ? "" : claim.getTitle()); + options.put("description", Helper.isNullOrEmpty(claim.getDescription()) ? "" : claim.getDescription()); + options.put("thumbnail_url", Helper.isNullOrEmpty(claim.getThumbnailUrl()) ? "" : claim.getThumbnailUrl()); + + if (!Helper.isNullOrEmpty(filePath)) { + options.put("file_path", filePath); + } + if (claim.getTags() != null && claim.getTags().size() > 0) { + options.put("tags", new ArrayList<>(claim.getTags())); + } + if (metadata.getFee() != null) { + options.put("fee_currency", metadata.getFee().getCurrency()); + options.put("fee_amount", amountFormat.format(new BigDecimal(metadata.getFee().getAmount()).doubleValue())); + } + if (claim.getSigningChannel() != null) { + options.put("channel_id", claim.getSigningChannel().getClaimId()); + } + if (metadata.getLanguages() != null && metadata.getLanguages().size() > 0) { + options.put("languages", metadata.getLanguages()); + } + if (!Helper.isNullOrEmpty(metadata.getLicense())) { + options.put("license", metadata.getLicense()); + } + if (!Helper.isNullOrEmpty(metadata.getLicenseUrl())) { + options.put("license_url", metadata.getLicenseUrl()); + } + + Claim claimResult = null; + try { + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_PUBLISH, options); + if (result.has("outputs")) { + JSONArray outputs = result.getJSONArray("outputs"); + for (int i = 0; i < outputs.length(); i++) { + JSONObject output = outputs.getJSONObject(i); + if (output.has("claim_id") && output.has("claim_op")) { + claimResult = Claim.claimFromOutput(output); + break; + } + } + } + } catch (ApiCallException | ClassCastException | JSONException ex) { + error = ex; + } + + return claimResult; + + } + protected void onPostExecute(Claim result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result != null) { + handler.onSuccess(result); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/PurchaseListTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/PurchaseListTask.java new file mode 100644 index 00000000..46037086 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/PurchaseListTask.java @@ -0,0 +1,70 @@ +package io.lbry.browser.tasks.claim; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class PurchaseListTask extends AsyncTask> { + private int page; + private int pageSize; + private ClaimSearchResultHandler handler; + private View progressView; + private Exception error; + + public PurchaseListTask(int page, int pageSize, View progressView, ClaimSearchResultHandler handler) { + this.page = page; + this.pageSize = pageSize; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected List doInBackground(Void... params) { + List claims = null; + try { + Map options = new HashMap<>(); + options.put("page", page); + options.put("page_size", pageSize); + options.put("resolve", true); + + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_PURCHASE_LIST, options); + JSONArray items = result.getJSONArray("items"); + claims = new ArrayList<>(); + for (int i = 0; i < items.length(); i++) { + Claim claim = Claim.fromJSONObject(items.getJSONObject(i).getJSONObject("claim")); + claims.add(claim); + + Lbry.addClaimToCache(claim); + } + } catch (ApiCallException | JSONException | ClassCastException ex) { + error = ex; + } + + return claims; + } + protected void onPostExecute(List claims) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (claims != null) { + handler.onSuccess(claims, claims.size() < pageSize); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java new file mode 100644 index 00000000..120c36b7 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java @@ -0,0 +1,54 @@ +package io.lbry.browser.tasks.claim; + +import android.os.AsyncTask; +import android.view.View; + +import java.util.Arrays; +import java.util.List; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class ResolveTask extends AsyncTask> { + private List urls; + private String connectionString; + private ClaimListResultHandler handler; + private View progressView; + private ApiCallException error; + + public ResolveTask(String url, String connectionString, View progressView, ClaimListResultHandler handler) { + this(Arrays.asList(url), connectionString, progressView, handler); + } + + public ResolveTask(List urls, String connectionString, View progressView, ClaimListResultHandler handler) { + this.urls = urls; + this.connectionString = connectionString; + this.progressView = progressView; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected List doInBackground(Void... params) { + try { + return Helper.filterInvalidReposts(Lbry.resolve(urls, connectionString)); + } catch (ApiCallException ex) { + error = ex; + return null; + } + } + protected void onPostExecute(List claims) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (claims != null) { + handler.onSuccess(claims); + } else { + handler.onError(error); + } + } + } + +} diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/StreamRepostTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/StreamRepostTask.java new file mode 100644 index 00000000..28ec4dcf --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/StreamRepostTask.java @@ -0,0 +1,77 @@ +package io.lbry.browser.tasks.claim; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class StreamRepostTask extends AsyncTask { + private String name; + private BigDecimal bid; + private String claimId; + private String channelId; + private View progressView; + private ClaimResultHandler handler; + private Exception error; + + public StreamRepostTask(String name, BigDecimal bid, String claimId, String channelId, View progressView, ClaimResultHandler handler) { + this.name = name; + this.bid = bid; + this.claimId = claimId; + this.channelId = channelId; + this.progressView = progressView; + this.handler = handler; + } + + protected Claim doInBackground(Void... params) { + Claim claimResult = null; + try { + Map options = new HashMap<>(); + options.put("name", name); + options.put("bid", new DecimalFormat(Helper.SDK_AMOUNT_FORMAT, new DecimalFormatSymbols(Locale.US)).format(bid.doubleValue())); + options.put("claim_id", claimId); + options.put("channel_id", channelId); + + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_STREAM_REPOST, options); + if (result.has("outputs")) { + JSONArray outputs = result.getJSONArray("outputs"); + for (int i = 0; i < outputs.length(); i++) { + JSONObject output = outputs.getJSONObject(i); + if (output.has("claim_id") && output.has("claim_op")) { + claimResult = Claim.claimFromOutput(output); + break; + } + } + } + } catch (ApiCallException | ClassCastException | JSONException ex) { + error = ex; + } + + return claimResult; + } + + protected void onPostExecute(Claim result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result != null) { + handler.onSuccess(result); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/file/BulkDeleteFilesTask.java b/app/src/main/java/io/lbry/browser/tasks/file/BulkDeleteFilesTask.java new file mode 100644 index 00000000..b05ec9d8 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/file/BulkDeleteFilesTask.java @@ -0,0 +1,32 @@ +package io.lbry.browser.tasks.file; + +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Lbry; + +// Just run delete on the specified claim IDs (no need for a handler) +public class BulkDeleteFilesTask extends AsyncTask { + private List claimIds; + public BulkDeleteFilesTask(List claimIds) { + this.claimIds = claimIds; + } + protected Boolean doInBackground(Void... params) { + for (String claimId : claimIds) { + try { + Map options = new HashMap<>(); + options.put("claim_id", claimId); + options.put("delete_from_download_dir", true); + Lbry.genericApiCall(Lbry.METHOD_FILE_DELETE, options); + } catch (ApiCallException ex) { + // pass + } + } + return true; + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/file/DeleteFileTask.java b/app/src/main/java/io/lbry/browser/tasks/file/DeleteFileTask.java new file mode 100644 index 00000000..bdc50bc4 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/file/DeleteFileTask.java @@ -0,0 +1,43 @@ +package io.lbry.browser.tasks.file; + +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Lbry; + +public class DeleteFileTask extends AsyncTask { + private String claimId; + private Exception error; + private GenericTaskHandler handler; + + public DeleteFileTask(String claimId, GenericTaskHandler handler) { + this.claimId = claimId; + this.handler = handler; + } + + protected Boolean doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("claim_id", claimId); + options.put("delete_from_download_dir", true); + return (boolean) Lbry.genericApiCall(Lbry.METHOD_FILE_DELETE, options); + } catch (ApiCallException ex) { + error = ex; + return false; + } + } + + protected void onPostExecute(Boolean result) { + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/file/FileListTask.java b/app/src/main/java/io/lbry/browser/tasks/file/FileListTask.java new file mode 100644 index 00000000..0b442d77 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/file/FileListTask.java @@ -0,0 +1,60 @@ +package io.lbry.browser.tasks.file; + +import android.os.AsyncTask; +import android.view.View; + +import java.util.List; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class FileListTask extends AsyncTask> { + private String claimId; + private boolean downloads; + private int page; + private int pageSize; + private FileListResultHandler handler; + private View progressView; + private ApiCallException error; + + public FileListTask(int page, int pageSize, boolean downloads, View progressView, FileListResultHandler handler) { + this(null, progressView, handler); + this.page = page; + this.pageSize = pageSize; + this.downloads = downloads; + } + + public FileListTask(String claimId, View progressView, FileListResultHandler handler) { + this.claimId = claimId; + this.progressView = progressView; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected List doInBackground(Void... params) { + try { + return Lbry.fileList(claimId, downloads, page, pageSize); + } catch (ApiCallException ex) { + error = ex; + return null; + } + } + protected void onPostExecute(List files) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (files != null) { + handler.onSuccess(files, files.size() < pageSize); + } else { + handler.onError(error); + } + } + } + + public interface FileListResultHandler { + void onSuccess(List files, boolean hasReachedEnd); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/file/GetFileTask.java b/app/src/main/java/io/lbry/browser/tasks/file/GetFileTask.java new file mode 100644 index 00000000..bf708c82 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/file/GetFileTask.java @@ -0,0 +1,72 @@ +package io.lbry.browser.tasks.file; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class GetFileTask extends AsyncTask { + private String uri; + private boolean saveFile; + private View progressView; + private GetFileHandler handler; + private Exception error; + + public GetFileTask(String uri, boolean saveFile, View progressView, GetFileHandler handler) { + this.uri = uri; + this.saveFile = saveFile; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + if (handler != null) { + handler.beforeStart(); + } + } + + protected LbryFile doInBackground(Void... params) { + LbryFile file = null; + try { + Map options = new HashMap<>(); + options.put("uri", uri); + options.put("save_file", saveFile); + JSONObject streamInfo = (JSONObject) Lbry.genericApiCall("get", options); + if (streamInfo.has("error")) { + throw new ApiCallException(Helper.getJSONString("error", "", streamInfo)); + } + + file = LbryFile.fromJSONObject(streamInfo); + } catch (ApiCallException ex) { + error = ex; + } + + return file; + } + + protected void onPostExecute(LbryFile file) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (file != null) { + handler.onSuccess(file, saveFile); + } else { + handler.onError(error, saveFile); + } + } + } + + public interface GetFileHandler { + void beforeStart(); + void onSuccess(LbryFile file, boolean saveFile); + void onError(Exception error, boolean saveFile); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/ChannelSubscribeTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/ChannelSubscribeTask.java new file mode 100644 index 00000000..89933903 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/ChannelSubscribeTask.java @@ -0,0 +1,85 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class ChannelSubscribeTask extends AsyncTask { + private Context context; + private String channelClaimId; + private Subscription subscription; + private ChannelSubscribeHandler handler; + private Exception error; + private boolean isUnsubscribing; + + public ChannelSubscribeTask(Context context, String channelClaimId, Subscription subscription, boolean isUnsubscribing, ChannelSubscribeHandler handler) { + this.context = context; + this.channelClaimId = channelClaimId; + this.subscription = subscription; + this.handler = handler; + this.isUnsubscribing = isUnsubscribing; + } + protected Boolean doInBackground(Void... params) { + SQLiteDatabase db = null; + try { + // Save to (or delete from) local store + if (context instanceof MainActivity) { + db = ((MainActivity) context).getDbHelper().getWritableDatabase(); + } + if (db != null) { + if (!isUnsubscribing) { + DatabaseHelper.createOrUpdateSubscription(subscription, db); + } else { + DatabaseHelper.deleteSubscription(subscription, db); + } + } + + // Save with Lbryio + Map options = new HashMap<>(); + options.put("claim_id", channelClaimId); + if (!isUnsubscribing) { + options.put("channel_name", subscription.getChannelName()); + } + + String action = isUnsubscribing ? "delete" : "new"; + Lbryio.call("subscription", action, options, context); + + if (!isUnsubscribing) { + Lbryio.addSubscription(subscription); + } else { + Lbryio.removeSubscription(subscription); + } + } catch (LbryioRequestException | LbryioResponseException | SQLiteException ex) { + error = ex; + return false; + } + + return true; + } + protected void onPostExecute(Boolean success) { + if (handler != null) { + if (success) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } + + public interface ChannelSubscribeHandler { + void onSuccess(); + void onError(Exception exception); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/ClaimRewardTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/ClaimRewardTask.java new file mode 100644 index 00000000..3a48c8e8 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/ClaimRewardTask.java @@ -0,0 +1,86 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.content.Context; +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONObject; + +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.R; +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.Lbryio; + +public class ClaimRewardTask extends AsyncTask { + + private Context context; + private String type; + private String claimCode; + private View progressView; + private double amountClaimed; + private ClaimRewardHandler handler; + private Exception error; + + public ClaimRewardTask(String type, String claimCode, View progressView, Context context, ClaimRewardHandler handler) { + this.type = type; + this.claimCode = claimCode; + this.progressView = progressView; + this.context = context; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + public String doInBackground(Void... params) { + String message = null; + try { + // Get a new wallet address for the reward + String address = (String) Lbry.genericApiCall(Lbry.METHOD_ADDRESS_UNUSED); + Map options = new HashMap<>(); + options.put("reward_type", type); + options.put("wallet_address", address); + if (!Helper.isNullOrEmpty(claimCode)) { + options.put("claim_code", claimCode); + + } + JSONObject reward = (JSONObject) Lbryio.parseResponse( + Lbryio.call("reward", "claim", options, Helper.METHOD_POST, null)); + amountClaimed = Helper.getJSONDouble("reward_amount", 0, reward); + String defaultMessage = context != null ? + context.getResources().getQuantityString( + R.plurals.claim_reward_message, + amountClaimed == 1 ? 1 : 2, + new DecimalFormat(Helper.LBC_CURRENCY_FORMAT_PATTERN).format(amountClaimed)) : ""; + message = Helper.getJSONString("reward_notification", defaultMessage, reward); + } catch (ApiCallException | LbryioRequestException | LbryioResponseException ex) { + error = ex; + } + + return message; + } + + protected void onPostExecute(String message) { + Helper.setViewVisibility(progressView, View.INVISIBLE); + if (handler != null) { + if (message != null) { + handler.onSuccess(amountClaimed, message); + } else { + handler.onError(error); + } + } + } + + public interface ClaimRewardHandler { + void onSuccess(double amountClaimed, String message); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchCurrentUserTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchCurrentUserTask.java new file mode 100644 index 00000000..3be8e03e --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchCurrentUserTask.java @@ -0,0 +1,38 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.os.AsyncTask; + +import io.lbry.browser.model.lbryinc.User; +import io.lbry.browser.utils.Lbryio; + +public class FetchCurrentUserTask extends AsyncTask { + private Exception error; + private FetchUserTaskHandler handler; + + public FetchCurrentUserTask(FetchUserTaskHandler handler) { + this.handler = handler; + } + protected User doInBackground(Void... params) { + try { + return Lbryio.fetchCurrentUser(null); + } catch (Exception ex) { + error = ex; + return null; + } + } + + protected void onPostExecute(User result) { + if (handler != null) { + if (result != null) { + handler.onSuccess(result); + } else { + handler.onError(error); + } + } + } + + public interface FetchUserTaskHandler { + void onSuccess(User user); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchInviteStatusTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchInviteStatusTask.java new file mode 100644 index 00000000..d56f7dbd --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchInviteStatusTask.java @@ -0,0 +1,72 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.model.lbryinc.Invitee; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class FetchInviteStatusTask extends AsyncTask> { + private FetchInviteStatusHandler handler; + private View progressView; + private Exception error; + + public FetchInviteStatusTask(View progressView, FetchInviteStatusHandler handler) { + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + protected List doInBackground(Void... params) { + List invitees = null; + try { + JSONObject status = (JSONObject) Lbryio.parseResponse(Lbryio.call("user", "invite_status", null, null)); + JSONArray inviteesArray = status.getJSONArray("invitees"); + invitees = new ArrayList<>(); + for (int i = 0; i < inviteesArray.length(); i++) { + JSONObject inviteeObject = inviteesArray.getJSONObject(i); + Invitee invitee = new Invitee(); + invitee.setEmail(Helper.getJSONString("email", null, inviteeObject)); + invitee.setInviteRewardClaimable(Helper.getJSONBoolean("invite_reward_claimable", false, inviteeObject)); + invitee.setInviteRewardClaimed(Helper.getJSONBoolean("invite_reward_claimed", false, inviteeObject)); + + if (!Helper.isNullOrEmpty(invitee.getEmail())) { + invitees.add(invitee); + } + } + } catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException ex) { + error = ex; + } + + return invitees; + } + + protected void onPostExecute(List invitees) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (invitees != null) { + handler.onSuccess(invitees); + } else { + handler.onError(error); + } + } + } + + public interface FetchInviteStatusHandler { + void onSuccess(List invitees); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchReferralCodeTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchReferralCodeTask.java new file mode 100644 index 00000000..af15d83a --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchReferralCodeTask.java @@ -0,0 +1,57 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class FetchReferralCodeTask extends AsyncTask { + private FetchReferralCodeHandler handler; + private View progressView; + private Exception error; + + public FetchReferralCodeTask(View progressView, FetchReferralCodeHandler handler) { + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + protected String doInBackground(Void... params) { + String referralCode = null; + try { + JSONArray results = (JSONArray) Lbryio.parseResponse(Lbryio.call("user_referral_code", "list", null, null)); + if (results.length() > 0) { + referralCode = results.getString(0); + } + } catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException ex) { + error = ex; + } + + return referralCode; + } + + protected void onPostExecute(String referralCode) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (!Helper.isNullOrEmpty(referralCode)) { + handler.onSuccess(referralCode); + } else { + handler.onError(error); + } + } + } + + public interface FetchReferralCodeHandler { + void onSuccess(String referralCode); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchRewardsTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchRewardsTask.java new file mode 100644 index 00000000..b9f18067 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchRewardsTask.java @@ -0,0 +1,66 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.model.lbryinc.Reward; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class FetchRewardsTask extends AsyncTask> { + private FetchRewardsHandler handler; + private View progressView; + private Exception error; + + public FetchRewardsTask(View progressView, FetchRewardsHandler handler) { + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + protected List doInBackground(Void... params) { + List rewards = null; + try { + Map options = new HashMap<>(); + options.put("multiple_rewards_per_type", "true"); + JSONArray results = (JSONArray) Lbryio.parseResponse(Lbryio.call("reward", "list", null, null)); + rewards = new ArrayList<>(); + for (int i = 0; i < results.length(); i++) { + rewards.add(Reward.fromJSONObject(results.getJSONObject(i))); + } + } catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException ex) { + error = ex; + } + + return rewards; + } + + protected void onPostExecute(List rewards) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (rewards != null) { + handler.onSuccess(rewards); + } else { + handler.onError(error); + } + } + } + + public interface FetchRewardsHandler { + void onSuccess(List rewards); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchStatCountTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchStatCountTask.java new file mode 100644 index 00000000..1f8bb465 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchStatCountTask.java @@ -0,0 +1,73 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class FetchStatCountTask extends AsyncTask { + public static final int STAT_VIEW_COUNT = 1; + public static final int STAT_SUB_COUNT = 2; + + private String claimId; + private int stat; + private FetchStatCountHandler handler; + private View progressView; + private Exception error; + + public FetchStatCountTask(int stat, String claimId, View progressView, FetchStatCountHandler handler) { + this.stat = stat; + this.claimId = claimId; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + protected Integer doInBackground(Void... params) { + int count = -1; + try { + if (stat != STAT_VIEW_COUNT && stat != STAT_SUB_COUNT) { + throw new LbryioRequestException("Invalid stat count specified."); + } + + JSONArray results = (JSONArray) + Lbryio.parseResponse(Lbryio.call( + stat == STAT_VIEW_COUNT ? "file" : "subscription", + stat == STAT_VIEW_COUNT ? "view_count" : "sub_count", + Lbryio.buildSingleParam("claim_id", claimId), + Helper.METHOD_GET, null)); + if (results.length() > 0) { + count = results.getInt(0); + } + } catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException ex) { + error = ex; + } + + return count; + } + + protected void onPostExecute(Integer count) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (count > -1) { + handler.onSuccess(count); + } else { + handler.onError(error); + } + } + } + + public interface FetchStatCountHandler { + void onSuccess(int count); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchSubscriptionsTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchSubscriptionsTask.java new file mode 100644 index 00000000..da886beb --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/FetchSubscriptionsTask.java @@ -0,0 +1,86 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; +import android.view.View; +import android.widget.ProgressBar; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; + +public class FetchSubscriptionsTask extends AsyncTask> { + private Context context; + private FetchSubscriptionsHandler handler; + private ProgressBar progressBar; + private Exception error; + + public FetchSubscriptionsTask(Context context, ProgressBar progressBar, FetchSubscriptionsHandler handler) { + this.context = context; + this.progressBar = progressBar; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressBar, View.VISIBLE); + } + protected List doInBackground(Void... params) { + List subscriptions = new ArrayList<>(); + SQLiteDatabase db = null; + try { + JSONArray array = (JSONArray) Lbryio.parseResponse(Lbryio.call("subscription", "list", context)); + if (context instanceof MainActivity) { + db = ((MainActivity) context).getDbHelper().getWritableDatabase(); + } + if (array != null) { + for (int i = 0; i < array.length(); i++) { + JSONObject item = array.getJSONObject(i); + String claimId = item.getString("claim_id"); + String channelName = item.getString("channel_name"); + + LbryUri url = new LbryUri(); + url.setChannelName(channelName); + url.setClaimId(claimId); + Subscription subscription = new Subscription(channelName, url.toString()); + subscriptions.add(subscription); + // Persist the subscription locally if it doesn't exist + if (db != null) { + DatabaseHelper.createOrUpdateSubscription(subscription, db); + } + } + } + } catch (ClassCastException | LbryioRequestException | LbryioResponseException | JSONException | IllegalStateException ex) { + error = ex; + return null; + } + + return subscriptions; + } + protected void onPostExecute(List subscriptions) { + Helper.setViewVisibility(progressBar, View.GONE); + if (handler != null) { + if (subscriptions != null) { + handler.onSuccess(subscriptions); + } else { + handler.onError(error); + } + } + } + + public interface FetchSubscriptionsHandler { + void onSuccess(List subscriptions); + void onError(Exception exception); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/InviteByEmailTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/InviteByEmailTask.java new file mode 100644 index 00000000..e2709862 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/InviteByEmailTask.java @@ -0,0 +1,55 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.os.AsyncTask; +import android.view.View; + + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class InviteByEmailTask extends AsyncTask { + private String email; + private View progressView; + private GenericTaskHandler handler; + private Exception error; + + public InviteByEmailTask(String email, View progressView, GenericTaskHandler handler) { + this.email = email; + this.progressView = progressView; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + if (handler != null) { + handler.beforeStart(); + } + } + protected Boolean doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("email", email); + Lbryio.parseResponse(Lbryio.call("user", "invite", options, Helper.METHOD_POST, null)); + } catch (LbryioRequestException | LbryioResponseException ex) { + error = ex; + return false; + } + + return true; + } + protected void onPostExecute(Boolean result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/LogFileViewTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/LogFileViewTask.java new file mode 100644 index 00000000..c0055a7c --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/LogFileViewTask.java @@ -0,0 +1,55 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.os.AsyncTask; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Lbryio; +import okhttp3.Response; + +public class LogFileViewTask extends AsyncTask { + private String uri; + private Claim claim; + private Exception error; + private GenericTaskHandler handler; + private long timeToStart; + + public LogFileViewTask(String uri, Claim claim, long timeToStart, GenericTaskHandler handler) { + this.uri = uri; + this.claim = claim; + this.timeToStart = timeToStart; + this.handler = handler; + } + protected Boolean doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("uri", uri); + options.put("claim_id", claim.getClaimId()); + options.put("outpoint", String.format("%s:%d", claim.getTxid(), claim.getNout())); + if (timeToStart > 0) { + options.put("time_to_start", String.valueOf(timeToStart)); + } + Lbryio.call("file", "view", options, null).close(); + } catch (LbryioRequestException | LbryioResponseException ex) { + error = ex; + return false; + } + return true; + } + protected void onPostExecute(Boolean result) { + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/LogPublishTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/LogPublishTask.java new file mode 100644 index 00000000..f807f360 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/LogPublishTask.java @@ -0,0 +1,35 @@ +package io.lbry.browser.tasks.lbryinc; + +import android.os.AsyncTask; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.utils.Lbryio; +import okhttp3.Response; + +public class LogPublishTask extends AsyncTask { + private Claim claimResult; + public LogPublishTask(Claim claimResult) { + this.claimResult = claimResult; + } + protected Void doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("uri", claimResult.getPermanentUrl()); + options.put("claim_id", claimResult.getClaimId()); + options.put("outpoint", String.format("%s:%d", claimResult.getTxid(), claimResult.getNout())); + if (claimResult.getSigningChannel() != null) { + options.put("channel_claim_id", claimResult.getSigningChannel().getClaimId()); + } + Lbryio.call("event", "publish", options, null).close(); + } catch (LbryioRequestException | LbryioResponseException ex) { + // pass + } + return null; + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/localdata/FetchRecentUrlHistoryTask.java b/app/src/main/java/io/lbry/browser/tasks/localdata/FetchRecentUrlHistoryTask.java new file mode 100644 index 00000000..852e87ae --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/localdata/FetchRecentUrlHistoryTask.java @@ -0,0 +1,37 @@ +package io.lbry.browser.tasks.localdata; + +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.os.AsyncTask; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.model.UrlSuggestion; + +public class FetchRecentUrlHistoryTask extends AsyncTask> { + private DatabaseHelper dbHelper; + private FetchRecentUrlHistoryHandler handler; + public FetchRecentUrlHistoryTask(DatabaseHelper dbHelper, FetchRecentUrlHistoryHandler handler) { + this.dbHelper = dbHelper; + this.handler = handler; + } + protected List doInBackground(Void... params) { + try { + SQLiteDatabase db = dbHelper.getReadableDatabase(); + return DatabaseHelper.getRecentHistory(db); + } catch (SQLiteException ex) { + return new ArrayList<>(); + } + } + protected void onPostExecute(List recentHistory) { + if (handler != null) { + handler.onSuccess(recentHistory); + } + } + + public interface FetchRecentUrlHistoryHandler { + void onSuccess(List recentHistory); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/localdata/FetchViewHistoryTask.java b/app/src/main/java/io/lbry/browser/tasks/localdata/FetchViewHistoryTask.java new file mode 100644 index 00000000..fd0d4b21 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/localdata/FetchViewHistoryTask.java @@ -0,0 +1,46 @@ +package io.lbry.browser.tasks.localdata; + +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.os.AsyncTask; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.model.UrlSuggestion; +import io.lbry.browser.model.ViewHistory; +import io.lbry.browser.utils.Helper; + +public class FetchViewHistoryTask extends AsyncTask> { + private DatabaseHelper dbHelper; + private FetchViewHistoryHandler handler; + private int pageSize; + private Date lastDate; + public FetchViewHistoryTask(Date lastDate, int pageSize, DatabaseHelper dbHelper, FetchViewHistoryHandler handler) { + this.lastDate = lastDate; + this.pageSize = pageSize; + this.dbHelper = dbHelper; + this.handler = handler; + } + protected List doInBackground(Void... params) { + try { + SQLiteDatabase db = dbHelper.getReadableDatabase(); + return DatabaseHelper.getViewHistory( + lastDate == null ? null : new SimpleDateFormat(Helper.ISO_DATE_FORMAT_PATTERN).format(lastDate), pageSize, db); + } catch (SQLiteException ex) { + return new ArrayList<>(); + } + } + protected void onPostExecute(List history) { + if (handler != null) { + handler.onSuccess(history, history.size() < pageSize); + } + } + + public interface FetchViewHistoryHandler { + void onSuccess(List history, boolean hasReachedEnd); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/localdata/LoadGalleryItemsTask.java b/app/src/main/java/io/lbry/browser/tasks/localdata/LoadGalleryItemsTask.java new file mode 100644 index 00000000..5676f072 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/localdata/LoadGalleryItemsTask.java @@ -0,0 +1,136 @@ +package io.lbry.browser.tasks.localdata; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.AsyncTask; +import android.provider.MediaStore; +import android.util.Log; +import android.view.View; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.model.GalleryItem; +import io.lbry.browser.utils.Helper; + +public class LoadGalleryItemsTask extends AsyncTask> { + private static final String TAG = "LoadGalleryItemsTask"; + private LoadGalleryHandler handler; + private View progressView; + private Context context; + + public LoadGalleryItemsTask(View progressView, Context context, LoadGalleryHandler handler) { + this.progressView = progressView; + this.context = context; + this.handler = handler; + } + + protected void onPreExecute(Void... params) { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + protected List doInBackground(Void... params) { + List items = new ArrayList<>(); + List itemsWithThumbnails = new ArrayList<>(); + Cursor cursor = null; + if (context != null) { + ContentResolver resolver = context.getContentResolver(); + try { + String[] projection = { + MediaStore.MediaColumns._ID, + MediaStore.MediaColumns.DATA, + MediaStore.MediaColumns.DISPLAY_NAME, + MediaStore.MediaColumns.MIME_TYPE, + MediaStore.Video.Media.DURATION + }; + cursor = resolver.query( + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + projection, null, null, + String.format("%s DESC", MediaStore.MediaColumns.DATE_MODIFIED)); + while (cursor.moveToNext()) { + int idColumn = cursor.getColumnIndex(MediaStore.MediaColumns._ID); + int nameColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); + int typeColumn = cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE); + int pathColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DATA); + int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION); + + GalleryItem item = new GalleryItem(); + item.setId(cursor.getString(idColumn)); + item.setName(cursor.getString(nameColumn)); + item.setType(cursor.getString(typeColumn)); + item.setFilePath(cursor.getString(pathColumn)); + item.setDuration(cursor.getLong(durationColumn)); + items.add(item); + } + } catch (SQLiteException ex) { + + // failed to load videos. log and pass + Log.e(TAG, ex.getMessage(), ex); + } finally { + Helper.closeCursor(cursor); + } + + // load (or generate) thumbnail for each item + for (GalleryItem item : items) { + String id = item.getId(); + File cacheDir = context.getExternalCacheDir(); + File thumbnailsDir = new File(String.format("%s/thumbnails", cacheDir.getAbsolutePath())); + if (!thumbnailsDir.isDirectory()) { + thumbnailsDir.mkdirs(); + } + + String thumbnailPath = String.format("%s/%s.png", thumbnailsDir.getAbsolutePath(), id); + File file = new File(thumbnailPath); + if (!file.exists()) { + // save the thumbnail to the path + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = 1; + Bitmap thumbnail = MediaStore.Video.Thumbnails.getThumbnail( + resolver, Long.parseLong(id), MediaStore.Video.Thumbnails.MINI_KIND, options); + if (thumbnail != null) { + try (FileOutputStream os = new FileOutputStream(thumbnailPath)) { + thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os); + } catch (IOException ex) { + // skip + } + } + } + + if (file.exists() && file.length() > 0) { + item.setThumbnailPath(file.getAbsolutePath()); + itemsWithThumbnails.add(item); + publishProgress(item); + } + } + } + + return itemsWithThumbnails; + } + + protected void onProgressUpdate(GalleryItem... items) { + if (handler != null) { + for (GalleryItem item : items) { + handler.onItemLoaded(item); + } + } + } + + protected void onPostExecute(List items) { + if (handler != null) { + handler.onAllItemsLoaded(items); + } + } + + public interface LoadGalleryHandler { + void onItemLoaded(GalleryItem item); + void onAllItemsLoaded(List items); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/localdata/SaveUrlHistoryTask.java b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveUrlHistoryTask.java new file mode 100644 index 00000000..a5778984 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveUrlHistoryTask.java @@ -0,0 +1,49 @@ +package io.lbry.browser.tasks.localdata; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.model.UrlSuggestion; +import io.lbry.browser.tasks.GenericTaskHandler; + +public class SaveUrlHistoryTask extends AsyncTask { + private DatabaseHelper dbHelper; + private UrlSuggestion suggestion; + private SaveUrlHistoryHandler handler; + private Exception error; + + public SaveUrlHistoryTask(UrlSuggestion suggestion, DatabaseHelper dbHelper, SaveUrlHistoryHandler handler) { + this.suggestion = suggestion; + this.dbHelper = dbHelper; + this.handler = handler; + + } + protected Boolean doInBackground(Void... params) { + try { + SQLiteDatabase db = dbHelper.getWritableDatabase(); + DatabaseHelper.createOrUpdateUrlHistoryItem(suggestion.getText(), suggestion.getUri().toString(), suggestion.getType(), db); + } catch (Exception ex) { + error = ex; + return false; + } + + return true; + } + protected void onPostExecute(Boolean result) { + if (handler != null) { + if (result) { + handler.onSuccess(suggestion); + } else { + handler.onError(error); + } + } + } + + public interface SaveUrlHistoryHandler { + void onSuccess(UrlSuggestion item); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/localdata/SaveViewHistoryTask.java b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveViewHistoryTask.java new file mode 100644 index 00000000..bb7e0f64 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveViewHistoryTask.java @@ -0,0 +1,45 @@ +package io.lbry.browser.tasks.localdata; + +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; + +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.model.ViewHistory; + +public class SaveViewHistoryTask extends AsyncTask { + private DatabaseHelper dbHelper; + private ViewHistory history; + private SaveViewHistoryHandler handler; + private Exception error; + + public SaveViewHistoryTask(ViewHistory history, DatabaseHelper dbHelper, SaveViewHistoryHandler handler) { + this.history = history; + this.dbHelper = dbHelper; + this.handler = handler; + } + protected Boolean doInBackground(Void... params) { + try { + SQLiteDatabase db = dbHelper.getWritableDatabase(); + DatabaseHelper.createOrUpdateViewHistoryItem(history, db); + } catch (Exception ex) { + error = ex; + return false; + } + + return true; + } + protected void onPostExecute(Boolean result) { + if (handler != null) { + if (result) { + handler.onSuccess(history); + } else { + handler.onError(error); + } + } + } + + public interface SaveViewHistoryHandler { + void onSuccess(ViewHistory item); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/verification/CheckUserEmailVerifiedTask.java b/app/src/main/java/io/lbry/browser/tasks/verification/CheckUserEmailVerifiedTask.java new file mode 100644 index 00000000..f058b7b5 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/verification/CheckUserEmailVerifiedTask.java @@ -0,0 +1,30 @@ +package io.lbry.browser.tasks.verification; + +import android.os.AsyncTask; + +import io.lbry.browser.model.lbryinc.User; +import io.lbry.browser.utils.Lbryio; + +public class CheckUserEmailVerifiedTask extends AsyncTask { + private CheckUserEmailVerifiedHandler handler; + + public CheckUserEmailVerifiedTask(CheckUserEmailVerifiedHandler handler) { + this.handler = handler; + } + + protected Boolean doInBackground(Void... params) { + User user = Lbryio.fetchCurrentUser(null); + return user != null && user.isHasVerifiedEmail(); + } + + protected void onPostExecute(Boolean result) { + if (handler != null && result) { + // we only care if the user has actually verified their email + handler.onUserEmailVerified(); + } + } + + public interface CheckUserEmailVerifiedHandler { + void onUserEmailVerified(); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/verification/EmailNewTask.java b/app/src/main/java/io/lbry/browser/tasks/verification/EmailNewTask.java new file mode 100644 index 00000000..f699835e --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/verification/EmailNewTask.java @@ -0,0 +1,81 @@ +package io.lbry.browser.tasks.verification; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class EmailNewTask extends AsyncTask { + private String email; + private View progressView; + private EmailNewHandler handler; + private Exception error; + + public EmailNewTask(String email, View progressView, EmailNewHandler handler) { + this.email = email; + this.progressView = progressView; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + if (handler != null) { + handler.beforeStart(); + } + } + protected Boolean doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("email", email); + options.put("send_verification_email", "true"); + Lbryio.parseResponse(Lbryio.call("user_email", "new", options, Helper.METHOD_POST, null)); + } catch (LbryioResponseException ex) { + if (ex.getStatusCode() == 409) { + if (handler != null) { + handler.onEmailExists(); + } + + // email already exists + Map options = new HashMap<>(); + options.put("email", email); + options.put("only_if_expired", "true"); + try { + Lbryio.parseResponse(Lbryio.call("user_email", "resend_token", options, Helper.METHOD_POST, null)); + } catch (LbryioRequestException | LbryioResponseException e) { + error = e; + return false; + } + } else { + error = ex; + return false; + } + } catch (LbryioRequestException ex) { + error = ex; + return false; + } + + return true; + } + protected void onPostExecute(Boolean result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } + + public interface EmailNewHandler extends GenericTaskHandler { + void onEmailExists(); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/verification/EmailResendTask.java b/app/src/main/java/io/lbry/browser/tasks/verification/EmailResendTask.java new file mode 100644 index 00000000..715acb30 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/verification/EmailResendTask.java @@ -0,0 +1,54 @@ +package io.lbry.browser.tasks.verification; + +import android.os.AsyncTask; +import android.view.View; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class EmailResendTask extends AsyncTask { + private String email; + private View progressView; + private GenericTaskHandler handler; + private Exception error; + + public EmailResendTask(String email, View progressView, GenericTaskHandler handler) { + this.email = email; + this.progressView = progressView; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + if (handler != null) { + handler.beforeStart(); + } + } + protected Boolean doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("email", email); + Lbryio.parseResponse(Lbryio.call("user_email", "resend_token", options, Helper.METHOD_POST, null)); + } catch (LbryioRequestException | LbryioResponseException ex) { + error = ex; + return false; + } + + return true; + } + protected void onPostExecute(Boolean result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/verification/PhoneNewVerifyTask.java b/app/src/main/java/io/lbry/browser/tasks/verification/PhoneNewVerifyTask.java new file mode 100644 index 00000000..15730464 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/verification/PhoneNewVerifyTask.java @@ -0,0 +1,65 @@ +package io.lbry.browser.tasks.verification; + +import android.os.AsyncTask; +import android.view.View; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class PhoneNewVerifyTask extends AsyncTask { + private String countryCode; + private String phoneNumber; + private String verificationCode; + private View progressView; + private GenericTaskHandler handler; + private Exception error; + + public PhoneNewVerifyTask(String countryCode, String phoneNumber, String verificationCode, View progressView, GenericTaskHandler handler) { + this.countryCode = countryCode; + this.phoneNumber = phoneNumber; + this.verificationCode = verificationCode; + this.progressView = progressView; + this.handler = handler; + } + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + if (handler != null) { + handler.beforeStart(); + } + } + protected Boolean doInBackground(Void... params) { + try { + boolean isVerify = !Helper.isNullOrEmpty(verificationCode); + Map options = new HashMap<>(); + options.put("country_code", countryCode); + options.put("phone_number", phoneNumber.replace(" ", "").replace("-", "")); + if (isVerify) { + options.put("verification_code", verificationCode); + } + + String action = isVerify ? "phone_number_confirm" : "phone_number_new"; + Lbryio.parseResponse(Lbryio.call("user", action, options, Helper.METHOD_POST, null)); + } catch (LbryioResponseException | LbryioRequestException ex) { + error = ex; + return false; + } + + return true; + } + protected void onPostExecute(Boolean result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/verification/PhoneResendTask.java b/app/src/main/java/io/lbry/browser/tasks/verification/PhoneResendTask.java new file mode 100644 index 00000000..247646b0 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/verification/PhoneResendTask.java @@ -0,0 +1,4 @@ +package io.lbry.browser.tasks.verification; + +public class PhoneResendTask { +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/DefaultSyncTaskHandler.java b/app/src/main/java/io/lbry/browser/tasks/wallet/DefaultSyncTaskHandler.java new file mode 100644 index 00000000..8e7a5d53 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/DefaultSyncTaskHandler.java @@ -0,0 +1,27 @@ +package io.lbry.browser.tasks.wallet; + +import io.lbry.browser.model.WalletSync; + +public abstract class DefaultSyncTaskHandler implements SyncTaskHandler { + public void onSyncGetSuccess(WalletSync walletSync) { + throw new UnsupportedOperationException(); + } + public void onSyncGetWalletNotFound() { + throw new UnsupportedOperationException(); + } + public void onSyncGetError(Exception error) { + throw new UnsupportedOperationException(); + } + public void onSyncSetSuccess(String hash) { + throw new UnsupportedOperationException(); + } + public void onSyncSetError(Exception error) { + throw new UnsupportedOperationException(); + } + public void onSyncApplySuccess(String hash, String data) { + throw new UnsupportedOperationException(); + } + public void onSyncApplyError(Exception error) { + throw new UnsupportedOperationException(); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/LoadSharedUserStateTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/LoadSharedUserStateTask.java new file mode 100644 index 00000000..ac941123 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/LoadSharedUserStateTask.java @@ -0,0 +1,138 @@ +package io.lbry.browser.tasks.wallet; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.os.AsyncTask; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.model.Tag; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryUri; + +/* + version: '0.1', + value: { + subscriptions?: Array, + tags?: Array, + blocked?: Array, + settings?: any, + app_welcome_version?: number, + sharing_3P?: boolean, + }, + */ +public class LoadSharedUserStateTask extends AsyncTask { + private static final String KEY = "shared"; + + private Context context; + private LoadSharedUserStateHandler handler; + private Exception error; + + private List subscriptions; + private List followedTags; + + public LoadSharedUserStateTask(Context context, LoadSharedUserStateHandler handler) { + this.context = context; + this.handler = handler; + } + + protected Boolean doInBackground(Void... params) { + // data to save + // current subscriptions + // Get the previous saved state + try { + SQLiteDatabase db = null; + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_PREFERENCE_GET, Lbry.buildSingleParam("key", KEY)); + if (result != null) { + if (context instanceof MainActivity) { + db = ((MainActivity) context).getDbHelper().getWritableDatabase(); + } + + JSONObject shared = result.getJSONObject("shared"); + if (shared.has("type") + && "object".equalsIgnoreCase(shared.getString("type")) + && shared.has("value")) { + JSONObject value = shared.getJSONObject("value"); + + JSONArray subscriptionUrls = + value.has("subscriptions") && !value.isNull("subscriptions") ? value.getJSONArray("subscriptions") : null; + JSONArray tags = + value.has("tags") && !value.isNull("tags") ? value.getJSONArray("tags") : null; + + if (subscriptionUrls != null) { + subscriptions = new ArrayList<>(); + for (int i = 0; i < subscriptionUrls.length(); i++) { + String url = subscriptionUrls.getString(i); + try { + LbryUri uri = LbryUri.parse(LbryUri.normalize(url)); + Subscription subscription = new Subscription(); + subscription.setChannelName(uri.getChannelName()); + subscription.setUrl(url); + subscriptions.add(subscription); + if (db != null) { + DatabaseHelper.createOrUpdateSubscription(subscription, db); + } + } catch (LbryUriException | SQLiteException | IllegalStateException ex) { + // pass + } + } + } + + if (tags != null) { + if (db != null && tags.length() > 0) { + DatabaseHelper.setAllTagsUnfollowed(db); + } + + followedTags = new ArrayList<>(); + for (int i = 0; i < tags.length(); i++) { + String tagName = tags.getString(i); + Tag tag = new Tag(tagName); + tag.setFollowed(true); + followedTags.add(tag); + + try { + if (db != null) { + DatabaseHelper.createOrUpdateTag(tag, db); + } + } catch (SQLiteException ex) { + // pass + } + } + } + } + } + + return true; + } catch (ApiCallException | JSONException ex) { + // failed + error = ex; + } + return false; + } + + protected void onPostExecute(Boolean result) { + if (handler != null) { + if (result) { + handler.onSuccess(subscriptions, followedTags); + } else { + handler.onError(error); + } + } + } + + public interface LoadSharedUserStateHandler { + void onSuccess(List subscriptions, List followedTags); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/SaveSharedUserStateTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/SaveSharedUserStateTask.java new file mode 100644 index 00000000..9a0559af --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/SaveSharedUserStateTask.java @@ -0,0 +1,108 @@ +package io.lbry.browser.tasks.wallet; + +import android.os.AsyncTask; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.Lbryio; + +/* + version: '0.1', + value: { + subscriptions?: Array, + tags?: Array, + blocked?: Array, + settings?: any, + app_welcome_version?: number, + sharing_3P?: boolean, + }, + */ +public class SaveSharedUserStateTask extends AsyncTask { + private static final String KEY = "shared"; + private static final String VERSION = "0.1"; + private SaveSharedUserStateHandler handler; + private Exception error; + + public SaveSharedUserStateTask(SaveSharedUserStateHandler handler) { + this.handler = handler; + } + + protected Boolean doInBackground(Void... params) { + // data to save + // current subscriptions + List subscriptionUrls = new ArrayList<>(); + for (Subscription subscription : Lbryio.subscriptions) { + subscriptionUrls.add(subscription.getUrl()); + } + + // followed tags + List followedTags = Helper.getTagsForTagObjects(Lbry.followedTags); + + // Get the previous saved state + try { + boolean isExistingValid = false; + JSONObject sharedObject = null; + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_PREFERENCE_GET, Lbry.buildSingleParam("key", KEY)); + if (result != null) { + JSONObject shared = result.getJSONObject("shared"); + if (shared.has("type") + && "object".equalsIgnoreCase(shared.getString("type")) + && shared.has("value")) { + isExistingValid = true; + JSONObject value = shared.getJSONObject("value"); + value.put("subscriptions", Helper.jsonArrayFromList(subscriptionUrls)); + value.put("tags", Helper.jsonArrayFromList(followedTags)); + sharedObject = shared; + } + } + + if (!isExistingValid) { + // build a new object + JSONObject value = new JSONObject(); + value.put("subscriptions", Helper.jsonArrayFromList(subscriptionUrls)); + value.put("tags", Helper.jsonArrayFromList(followedTags)); + + sharedObject = new JSONObject(); + sharedObject.put("type", "object"); + sharedObject.put("value", value); + sharedObject.put("version", VERSION); + } + + Map options = new HashMap<>(); + options.put("key", KEY); + options.put("value", sharedObject.toString()); + Lbry.genericApiCall(Lbry.METHOD_PREFERENCE_SET, options); + + return true; + } catch (ApiCallException | JSONException ex) { + // failed + error = ex; + } + return false; + } + + protected void onPostExecute(Boolean result) { + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } + + public interface SaveSharedUserStateHandler { + void onSuccess(); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/SupportCreateTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/SupportCreateTask.java new file mode 100644 index 00000000..d571898a --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/SupportCreateTask.java @@ -0,0 +1,65 @@ +package io.lbry.browser.tasks.wallet; + +import android.os.AsyncTask; +import android.view.View; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class SupportCreateTask extends AsyncTask { + private String claimId; + private BigDecimal amount; + private boolean tip; + private View progressView; + private GenericTaskHandler handler; + private Exception error; + + public SupportCreateTask(String claimId, BigDecimal amount, boolean tip, View progressView, GenericTaskHandler handler) { + this.claimId = claimId; + this.amount = amount; + this.tip = tip; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + if (handler != null) { + handler.beforeStart(); + } + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected Boolean doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("claim_id", claimId); + options.put("amount", new DecimalFormat(Helper.SDK_AMOUNT_FORMAT, new DecimalFormatSymbols(Locale.US)).format(amount.doubleValue())); + options.put("tip", tip); + Lbry.genericApiCall(Lbry.METHOD_SUPPORT_CREATE, options); + } catch (ApiCallException ex) { + error = ex; + return false; + } + + return true; + } + + protected void onPostExecute(Boolean result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/SyncApplyTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/SyncApplyTask.java new file mode 100644 index 00000000..51572f49 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/SyncApplyTask.java @@ -0,0 +1,75 @@ +package io.lbry.browser.tasks.wallet; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class SyncApplyTask extends AsyncTask { + // flag to indicate if this sync_apply is to fetch wallet data or apply data + private boolean fetch; + private Exception error; + private String password; + private String data; + private View progressView; + private SyncTaskHandler handler; + + private String syncHash; + private String syncData; + + public SyncApplyTask(boolean fetch, String password, SyncTaskHandler handler) { + this.fetch = fetch; + this.password = password; + this.handler = handler; + } + + public SyncApplyTask(String password, String data, View progressView, SyncTaskHandler handler) { + this.password = password; + this.data = data; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + + public Boolean doInBackground(Void... params) { + Map options = new HashMap<>(); + options.put("password", Helper.isNullOrEmpty(password) ? "" : password); + if (!fetch) { + options.put("data", data); + options.put("blocking", true); + } + + try { + JSONObject response = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_SYNC_APPLY, options); + syncHash = Helper.getJSONString("hash", null, response); + syncData = Helper.getJSONString("data", null, response); + } catch (ApiCallException ex) { + error = ex; + return false; + } + + return true; + } + + protected void onPostExecute(Boolean result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result) { + handler.onSyncApplySuccess(syncHash, syncData); + } else { + handler.onSyncApplyError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/SyncGetTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/SyncGetTask.java new file mode 100644 index 00000000..c6c85b0f --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/SyncGetTask.java @@ -0,0 +1,116 @@ +package io.lbry.browser.tasks.wallet; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.exceptions.WalletException; +import io.lbry.browser.model.WalletSync; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.Lbryio; + +public class SyncGetTask extends AsyncTask { + + private boolean applySyncChanges; + private boolean applySyncSuccessful; + private Exception error; + private Exception syncApplyError; + private String password; + private SyncTaskHandler handler; + private View progressView; + + private String syncHash; + private String syncData; + + public SyncGetTask(String password, boolean applySyncChanges, View progressView, SyncTaskHandler handler) { + this.password = password; + this.progressView = progressView; + this.applySyncChanges = applySyncChanges; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected WalletSync doInBackground(Void... params) { + try { + password = Helper.isNullOrEmpty(password) ? "" : password; + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_WALLET_STATUS); + boolean isLocked = Helper.getJSONBoolean("is_locked", false, result); + boolean unlockSuccessful = + !isLocked || (boolean) Lbry.genericApiCall(Lbry.METHOD_WALLET_UNLOCK, Lbry.buildSingleParam("password", password)); + if (!unlockSuccessful) { + throw new WalletException("The wallet could not be unlocked with the provided password."); + } + + String hash = (String) Lbry.genericApiCall(Lbry.METHOD_SYNC_HASH); + try { + JSONObject response = (JSONObject) Lbryio.parseResponse( + Lbryio.call("sync", "get", Lbryio.buildSingleParam("hash", hash), Helper.METHOD_POST, null)); + WalletSync walletSync = new WalletSync( + Helper.getJSONString("hash", null, response), + Helper.getJSONString("data", null, response), + Helper.getJSONBoolean("changed", false, response) + ); + if (applySyncChanges && (!hash.equalsIgnoreCase(walletSync.getHash()) || walletSync.isChanged())) { + //Lbry.sync_apply({ password, data: response.data, blocking: true }); + try { + Map options = new HashMap<>(); + options.put("password", Helper.isNullOrEmpty(password) ? "" : password); + options.put("data", walletSync.getData()); + options.put("blocking", true); + + JSONObject syncApplyResponse = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_SYNC_APPLY, options); + syncHash = Helper.getJSONString("hash", null, syncApplyResponse); + syncData = Helper.getJSONString("data", null, syncApplyResponse); + applySyncSuccessful = true; + } catch (ApiCallException | ClassCastException ex) { + // sync_apply failed + syncApplyError = ex; + } + } + + if (Lbryio.isSignedIn() && !Lbryio.userHasSyncedWallet) { + // indicate that the user owns a synced wallet (only if the user is signed in) + Lbryio.userHasSyncedWallet = true; + } + + return walletSync; + } catch (LbryioResponseException ex) { + // wallet sync data doesn't exist + return null; + } + } catch (ApiCallException | WalletException | ClassCastException | LbryioRequestException ex) { + error = ex; + return null; + } + } + protected void onPostExecute(WalletSync result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result != null) { + handler.onSyncGetSuccess(result); + } else if (error != null) { + handler.onSyncGetError(error); + } else { + handler.onSyncGetWalletNotFound(); + } + + if (applySyncChanges) { + if (applySyncSuccessful) { + handler.onSyncApplySuccess(syncHash, syncData); + } else { + handler.onSyncApplyError(syncApplyError); + } + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/SyncSetTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/SyncSetTask.java new file mode 100644 index 00000000..1f5c888f --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/SyncSetTask.java @@ -0,0 +1,53 @@ +package io.lbry.browser.tasks.wallet; + +import android.os.AsyncTask; + +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbryio; + +public class SyncSetTask extends AsyncTask { + private Exception error; + private String oldHash; + private String newHash; + private String data; + private SyncTaskHandler handler; + + public SyncSetTask(String oldHash, String newHash, String data, SyncTaskHandler handler) { + this.oldHash = oldHash; + this.newHash = newHash; + this.data = data; + this.handler = handler; + } + + protected String doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("old_hash", oldHash); + options.put("new_hash", newHash); + options.put("data", data); + JSONObject response = (JSONObject) Lbryio.parseResponse( + Lbryio.call("sync", "set", options, Helper.METHOD_POST, null)); + String hash = Helper.getJSONString("hash", null, response); + return hash; + } catch (LbryioRequestException | LbryioResponseException | ClassCastException ex) { + error = ex; + return null; + } + } + protected void onPostExecute(String hash) { + if (handler != null) { + if (!Helper.isNullOrEmpty(hash)) { + handler.onSyncSetSuccess(hash); + } else { + handler.onSyncSetError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/SyncTaskHandler.java b/app/src/main/java/io/lbry/browser/tasks/wallet/SyncTaskHandler.java new file mode 100644 index 00000000..fd1008e5 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/SyncTaskHandler.java @@ -0,0 +1,13 @@ +package io.lbry.browser.tasks.wallet; + +import io.lbry.browser.model.WalletSync; + +public interface SyncTaskHandler { + void onSyncGetSuccess(WalletSync walletSync); + void onSyncGetWalletNotFound(); + void onSyncGetError(Exception error); + void onSyncSetSuccess(String hash); + void onSyncSetError(Exception error); + void onSyncApplySuccess(String hash, String data); + void onSyncApplyError(Exception error); +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/TransactionListTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/TransactionListTask.java new file mode 100644 index 00000000..b5e35539 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/TransactionListTask.java @@ -0,0 +1,56 @@ +package io.lbry.browser.tasks.wallet; + +import android.os.AsyncTask; +import android.view.View; + +import java.util.List; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.Transaction; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class TransactionListTask extends AsyncTask> { + private int page; + private int pageSize; + private View progressView; + private TransactionListHandler handler; + private Exception error; + + public TransactionListTask(int page, int pageSize, View progressView, TransactionListHandler handler) { + this.page = page; + this.pageSize = pageSize; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected List doInBackground(Void... params) { + List transactions = null; + try { + transactions = Lbry.transactionList(page, pageSize); + } catch (ApiCallException ex) { + error = ex; + } + + return transactions; + } + + protected void onPostExecute(List transactions) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (transactions != null) { + handler.onSuccess(transactions, transactions.size() < pageSize); + } else { + handler.onError(error); + } + } + } + + public interface TransactionListHandler { + void onSuccess(List transactions, boolean hasReachedEnd); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/WalletAddressUnusedTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/WalletAddressUnusedTask.java new file mode 100644 index 00000000..437eb2ef --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/WalletAddressUnusedTask.java @@ -0,0 +1,55 @@ +package io.lbry.browser.tasks.wallet; + +import android.os.AsyncTask; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigDecimal; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class WalletAddressUnusedTask extends AsyncTask { + private WalletAddressUnusedHandler handler; + private Exception error; + + public WalletAddressUnusedTask(WalletAddressUnusedHandler handler) { + this.handler = handler; + } + + protected void onPreExecute() { + if (handler != null) { + handler.beforeStart(); + } + } + + protected String doInBackground(Void... params) { + String address = null; + try { + address = (String) Lbry.genericApiCall(Lbry.METHOD_ADDRESS_UNUSED); + } catch (ApiCallException | ClassCastException ex) { + error = ex; + } + + return address; + } + + protected void onPostExecute(String unusedAddress) { + if (handler != null) { + if (!Helper.isNullOrEmpty(unusedAddress)) { + handler.onSuccess(unusedAddress); + } else { + handler.onError(error); + } + } + } + + public interface WalletAddressUnusedHandler { + void beforeStart(); + void onSuccess(String newAddress); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/WalletBalanceTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/WalletBalanceTask.java new file mode 100644 index 00000000..09d26c42 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/WalletBalanceTask.java @@ -0,0 +1,58 @@ +package io.lbry.browser.tasks.wallet; + +import android.os.AsyncTask; + +import org.json.JSONObject; + +import java.math.BigDecimal; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class WalletBalanceTask extends AsyncTask { + private WalletBalanceHandler handler; + private Exception error; + + public WalletBalanceTask(WalletBalanceHandler handler) { + this.handler = handler; + } + + protected WalletBalance doInBackground(Void... params) { + WalletBalance balance = new WalletBalance(); + try { + JSONObject json = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_WALLET_BALANCE); + JSONObject reservedSubtotals = Helper.getJSONObject("reserved_subtotals", json); + + balance.setAvailable(new BigDecimal(Helper.getJSONString("available", "0", json))); + balance.setReserved(new BigDecimal(Helper.getJSONString("reserved", "0", json))); + balance.setTotal(new BigDecimal(Helper.getJSONString("total", "0", json))); + if (reservedSubtotals != null) { + balance.setClaims(new BigDecimal(Helper.getJSONString("claims", "0", reservedSubtotals))); + balance.setSupports(new BigDecimal(Helper.getJSONString("supports", "0", reservedSubtotals))); + balance.setTips(new BigDecimal(Helper.getJSONString("tips", "0", reservedSubtotals))); + } + } catch (ApiCallException | ClassCastException ex) { + error = ex; + return null; + } + + return balance; + } + + protected void onPostExecute(WalletBalance walletBalance) { + if (handler != null) { + if (walletBalance != null) { + handler.onSuccess(walletBalance); + } else { + handler.onError(error); + } + } + } + + public interface WalletBalanceHandler { + void onSuccess(WalletBalance walletBalance); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/wallet/WalletSendTask.java b/app/src/main/java/io/lbry/browser/tasks/wallet/WalletSendTask.java new file mode 100644 index 00000000..8487b0a2 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/wallet/WalletSendTask.java @@ -0,0 +1,60 @@ +package io.lbry.browser.tasks.wallet; + +import android.os.AsyncTask; +import android.view.View; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class WalletSendTask extends AsyncTask { + private String recipientAddress; + private String amount; + private View progressView; + private WalletSendHandler handler; + private Exception error; + + public WalletSendTask(String recipientAddress, String amount, View progressView, WalletSendHandler handler) { + this.recipientAddress = recipientAddress; + this.amount = amount; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + } + protected Boolean doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("addresses", Arrays.asList(recipientAddress)); + options.put("amount", amount); + Lbry.genericApiCall(Lbry.METHOD_WALLET_SEND, options); + } catch (ApiCallException ex) { + error = ex; + return false; + } + + return true; + } + + protected void onPostExecute(Boolean result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } + + public interface WalletSendHandler { + void onSuccess(); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/BaseFragment.java b/app/src/main/java/io/lbry/browser/ui/BaseFragment.java new file mode 100644 index 00000000..a6e6bd3e --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/BaseFragment.java @@ -0,0 +1,60 @@ +package io.lbry.browser.ui; + +import android.content.Context; + +import androidx.fragment.app.Fragment; + +import java.util.Map; + +import io.lbry.browser.MainActivity; +import lombok.Getter; +import lombok.Setter; + +public class BaseFragment extends Fragment { + @Getter + @Setter + private Map params; + + public boolean shouldHideGlobalPlayer() { + return false; + } + + public boolean shouldSuspendGlobalPlayer() { + return false; + } + + public void onStart() { + super.onStart(); + if (shouldSuspendGlobalPlayer()) { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity.suspendGlobalPlayer(context); + } + } + } + + public void onStop() { + if (shouldSuspendGlobalPlayer()) { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity.resumeGlobalPlayer(context); + } + } + super.onStop(); + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.setSelectedMenuItemForFragment(this); + + if (shouldHideGlobalPlayer()) { + activity.hideGlobalNowPlaying(); + } else { + activity.checkNowPlaying(); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelAboutFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelAboutFragment.java new file mode 100644 index 00000000..89d0bbe8 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelAboutFragment.java @@ -0,0 +1,71 @@ +package io.lbry.browser.ui.channel; + +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.text.HtmlCompat; +import androidx.fragment.app.Fragment; + +import io.lbry.browser.R; +import io.lbry.browser.utils.Helper; +import lombok.Setter; + +public class ChannelAboutFragment extends Fragment { + private View layoutWebsite; + private View layoutEmail; + private View layoutInfoArea; + private View layoutNoAboutInfo; + private TextView textWebsite; + private TextView textEmail; + private TextView textDescription; + + @Setter + private String website; + @Setter + private String email; + @Setter + private String description; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_channel_about, container, false); + + layoutInfoArea = root.findViewById(R.id.channel_about_info_area); + layoutNoAboutInfo = root.findViewById(R.id.channel_about_no_info_container); + layoutWebsite = root.findViewById(R.id.channel_about_website_container); + layoutEmail = root.findViewById(R.id.channel_about_email_container); + textWebsite = root.findViewById(R.id.channel_about_website); + textEmail = root.findViewById(R.id.channel_about_email); + textDescription = root.findViewById(R.id.channel_about_description); + + boolean noInfo = (Helper.isNullOrEmpty(website) && Helper.isNullOrEmpty(email) && Helper.isNullOrEmpty(description)); + layoutNoAboutInfo.setVisibility(noInfo ? View.VISIBLE : View.GONE); + layoutInfoArea.setVisibility(noInfo ? View.GONE : View.VISIBLE); + layoutWebsite.setVisibility(!Helper.isNullOrEmpty(website) ? View.VISIBLE : View.GONE); + layoutEmail.setVisibility(!Helper.isNullOrEmpty(email) ? View.VISIBLE : View.GONE); + textDescription.setVisibility(!Helper.isNullOrEmpty(description) ? View.VISIBLE : View.GONE); + + textWebsite.setLinksClickable(true); + textWebsite.setMovementMethod(LinkMovementMethod.getInstance()); + textWebsite.setText(!Helper.isNullOrEmpty(website) ? + HtmlCompat.fromHtml(String.format("%s", website, website), HtmlCompat.FROM_HTML_MODE_LEGACY) : null); + + textEmail.setText(email); + textDescription.setText(description); + + return root; + } + + public void refresh() { + textWebsite.setText(!Helper.isNullOrEmpty(website) ? + HtmlCompat.fromHtml(String.format("%s", website, website), HtmlCompat.FROM_HTML_MODE_LEGACY) : null); + + textEmail.setText(email); + textDescription.setText(description); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelContentFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelContentFragment.java new file mode 100644 index 00000000..3e3e804a --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelContentFragment.java @@ -0,0 +1,329 @@ +package io.lbry.browser.ui.channel; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.ClaimListAdapter; +import io.lbry.browser.dialog.ContentFromDialogFragment; +import io.lbry.browser.dialog.ContentSortDialogFragment; +import io.lbry.browser.listener.DownloadActionListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.tasks.claim.ClaimSearchResultHandler; +import io.lbry.browser.tasks.claim.ClaimSearchTask; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.Predefined; +import lombok.Setter; + +public class ChannelContentFragment extends Fragment implements DownloadActionListener, SharedPreferences.OnSharedPreferenceChangeListener { + + @Setter + private String channelId; + private View sortLink; + private View contentFromLink; + private TextView sortLinkText; + private TextView contentFromLinkText; + private RecyclerView contentList; + private int currentSortBy; + private int currentContentFrom; + private String contentReleaseTime; + private List contentSortOrder; + private View contentLoading; + private View bigContentLoading; + private View noContentView; + private ClaimListAdapter contentListAdapter; + private boolean contentClaimSearchLoading; + private boolean contentHasReachedEnd; + private int currentClaimSearchPage; + private ClaimSearchTask contentClaimSearchTask; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_channel_content, container, false); + + currentSortBy = ContentSortDialogFragment.ITEM_SORT_BY_TRENDING; + currentContentFrom = ContentFromDialogFragment.ITEM_FROM_PAST_WEEK; + + sortLink = root.findViewById(R.id.channel_content_sort_link); + contentFromLink = root.findViewById(R.id.channel_content_time_link); + + sortLinkText = root.findViewById(R.id.channel_content_sort_link_text); + contentFromLinkText = root.findViewById(R.id.channel_content_time_link_text); + + bigContentLoading = root.findViewById(R.id.channel_content_main_progress); + contentLoading = root.findViewById(R.id.channel_content_load_progress); + noContentView = root.findViewById(R.id.channel_content_no_claim_search_content); + + contentList = root.findViewById(R.id.channel_content_list); + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + contentList.setLayoutManager(llm); + contentList.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + if (contentClaimSearchLoading) { + return; + } + + LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager(); + if (lm != null) { + int visibleItemCount = lm.getChildCount(); + int totalItemCount = lm.getItemCount(); + int pastVisibleItems = lm.findFirstVisibleItemPosition(); + if (pastVisibleItems + visibleItemCount >= totalItemCount) { + if (!contentHasReachedEnd) { + // load more + currentClaimSearchPage++; + fetchClaimSearchContent(); + } + } + } + } + }); + + sortLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ContentSortDialogFragment dialog = ContentSortDialogFragment.newInstance(); + dialog.setCurrentSortByItem(currentSortBy); + dialog.setSortByListener(new ContentSortDialogFragment.SortByListener() { + @Override + public void onSortByItemSelected(int sortBy) { + onSortByChanged(sortBy); + } + }); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + dialog.show(activity.getSupportFragmentManager(), ContentSortDialogFragment.TAG); + } + } + }); + contentFromLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ContentFromDialogFragment dialog = ContentFromDialogFragment.newInstance(); + dialog.setCurrentFromItem(currentContentFrom); + dialog.setContentFromListener(new ContentFromDialogFragment.ContentFromListener() { + @Override + public void onContentFromItemSelected(int contentFromItem) { + onContentFromChanged(contentFromItem); + } + }); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + dialog.show(activity.getSupportFragmentManager(), ContentFromDialogFragment.TAG); + } + } + }); + + return root; + } + + private void onContentFromChanged(int contentFrom) { + currentContentFrom = contentFrom; + + // rebuild options and search + updateContentFromLinkText(); + contentReleaseTime = Helper.buildReleaseTime(currentContentFrom); + fetchClaimSearchContent(true); + } + + private void onSortByChanged(int sortBy) { + currentSortBy = sortBy; + + // rebuild options and search + Helper.setViewVisibility(contentFromLink, currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ? View.VISIBLE : View.GONE); + currentContentFrom = currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ? + (currentContentFrom == 0 ? ContentFromDialogFragment.ITEM_FROM_PAST_WEEK : currentContentFrom) : 0; + + updateSortByLinkText(); + contentSortOrder = Helper.buildContentSortOrder(currentSortBy); + contentReleaseTime = Helper.buildReleaseTime(currentContentFrom); + fetchClaimSearchContent(true); + } + + private void updateSortByLinkText() { + int stringResourceId = -1; + switch (currentSortBy) { + case ContentSortDialogFragment.ITEM_SORT_BY_NEW: default: stringResourceId = R.string.new_text; break; + case ContentSortDialogFragment.ITEM_SORT_BY_TOP: stringResourceId = R.string.top; break; + case ContentSortDialogFragment.ITEM_SORT_BY_TRENDING: stringResourceId = R.string.trending; break; + } + + Helper.setViewText(sortLinkText, stringResourceId); + } + + private void updateContentFromLinkText() { + int stringResourceId = -1; + switch (currentContentFrom) { + case ContentFromDialogFragment.ITEM_FROM_PAST_24_HOURS: stringResourceId = R.string.past_24_hours; break; + case ContentFromDialogFragment.ITEM_FROM_PAST_WEEK: default: stringResourceId = R.string.past_week; break; + case ContentFromDialogFragment.ITEM_FROM_PAST_MONTH: stringResourceId = R.string.past_month; break; + case ContentFromDialogFragment.ITEM_FROM_PAST_YEAR: stringResourceId = R.string.past_year; break; + case ContentFromDialogFragment.ITEM_FROM_ALL_TIME: stringResourceId = R.string.all_time; break; + } + + Helper.setViewText(contentFromLinkText, stringResourceId); + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context != null) { + ((MainActivity) context).addDownloadActionListener(this); + } + PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this); + fetchClaimSearchContent(); + } + + public void onPause() { + Context context = getContext(); + if (context != null) { + ((MainActivity) context).removeDownloadActionListener(this); + } + PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this); + super.onPause(); + } + + private Map buildContentOptions() { + Context context = getContext(); + boolean canShowMatureContent = false; + if (context != null) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + } + + return Lbry.buildClaimSearchOptions( + null, + null, + canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS), + Arrays.asList(channelId), + null, + getContentSortOrder(), + contentReleaseTime, + currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage, + Helper.CONTENT_PAGE_SIZE); + } + + private List getContentSortOrder() { + if (contentSortOrder == null) { + return Arrays.asList(Claim.ORDER_BY_RELEASE_TIME); + } + return contentSortOrder; + } + + private View getLoadingView() { + return (contentListAdapter == null || contentListAdapter.getItemCount() == 0) ? bigContentLoading : contentLoading; + } + + private void fetchClaimSearchContent() { + fetchClaimSearchContent(false); + } + + private void fetchClaimSearchContent(boolean reset) { + if (reset && contentListAdapter != null) { + contentListAdapter.clearItems(); + currentClaimSearchPage = 1; + } + + contentClaimSearchLoading = true; + Helper.setViewVisibility(noContentView, View.GONE); + Map claimSearchOptions = buildContentOptions(); + contentClaimSearchTask = new ClaimSearchTask(claimSearchOptions, Lbry.LBRY_TV_CONNECTION_STRING, getLoadingView(), new ClaimSearchResultHandler() { + @Override + public void onSuccess(List claims, boolean hasReachedEnd) { + if (contentListAdapter == null) { + contentListAdapter = new ClaimListAdapter(claims, getContext()); + contentListAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() { + @Override + public void onClaimClicked(Claim claim) { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (claim.getName().startsWith("@")) { + // channel claim + activity.openChannelClaim(claim); + } else { + activity.openFileClaim(claim); + } + } + } + }); + } else { + contentListAdapter.addItems(claims); + } + + if (contentList != null && contentList.getAdapter() == null) { + contentList.setAdapter(contentListAdapter); + } + + contentHasReachedEnd = hasReachedEnd; + contentClaimSearchLoading = false; + checkNoContent(); + } + + @Override + public void onError(Exception error) { + contentClaimSearchLoading = false; + checkNoContent(); + } + }); + contentClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void checkNoContent() { + boolean noContent = contentListAdapter == null || contentListAdapter.getItemCount() == 0; + Helper.setViewVisibility(noContentView, noContent ? View.VISIBLE : View.GONE); + } + + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (key.equalsIgnoreCase(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT)) { + fetchClaimSearchContent(true); + } + } + + public void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) { + if ("abort".equals(downloadAction)) { + if (contentListAdapter != null) { + contentListAdapter.clearFileForClaimOrUrl(outpoint, uri); + } + return; + } + + try { + JSONObject fileInfo = new JSONObject(fileInfoJson); + LbryFile claimFile = LbryFile.fromJSONObject(fileInfo); + String claimId = claimFile.getClaimId(); + if (contentListAdapter != null) { + contentListAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri); + } + } catch (JSONException ex) { + // invalid file info for download + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFormFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFormFragment.java new file mode 100644 index 00000000..441ba9b1 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFormFragment.java @@ -0,0 +1,697 @@ +package io.lbry.browser.ui.channel; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.core.content.ContextCompat; +import androidx.core.widget.NestedScrollView; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.google.android.flexbox.FlexboxLayoutManager; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.TextInputEditText; + +import java.io.File; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.BuildConfig; +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.TagListAdapter; +import io.lbry.browser.listener.FilePickerListener; +import io.lbry.browser.listener.StoragePermissionListener; +import io.lbry.browser.listener.WalletBalanceListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.NavMenuItem; +import io.lbry.browser.model.Tag; +import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.tasks.UpdateSuggestedTagsTask; +import io.lbry.browser.tasks.UploadImageTask; +import io.lbry.browser.tasks.claim.ChannelCreateUpdateTask; +import io.lbry.browser.tasks.claim.ClaimResultHandler; +import io.lbry.browser.tasks.lbryinc.LogPublishTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.LbryUri; +import lombok.Getter; + +public class ChannelFormFragment extends BaseFragment implements + FilePickerListener, StoragePermissionListener, TagListAdapter.TagClickListener, WalletBalanceListener { + + private static final int SUGGESTED_LIMIT = 8; + + @Getter + private boolean saveInProgress; + private boolean uploading; + private Claim currentClaim; + private boolean editFieldsLoaded; + private boolean editMode; + private View linkCancel; + private TextView linkShowOptional; + private MaterialButton buttonSave; + + private NestedScrollView scrollView; + private View inlineBalanceContainer; + private TextView inlineBalanceValue; + private View uploadProgress; + private View containerOptionalFields; + private ImageView imageCover; + private ImageView imageThumbnail; + private TextInputEditText inputTitle; + private TextInputEditText inputChannelName; + private TextInputEditText inputDeposit; + private TextInputEditText inputDescription; + private TextInputEditText inputWebsite; + private TextInputEditText inputEmail; + + private TextInputEditText inputTagFilter; + private RecyclerView addedTagsList; + private RecyclerView suggestedTagsList; + private TagListAdapter addedTagsAdapter; + private TagListAdapter suggestedTagsAdapter; + private View noTagsView; + private View noTagResultsView; + + private View coverEditArea; + private View iconContainer; + private View channelSaveProgress; + + private boolean launchCoverSelectPending; + private boolean launchThumbnailSelectPending; + private boolean coverFilePickerActive; + private boolean thumbnailFilePickerActive; + + private String currentFilter; + + private String coverUrl; + private String thumbnailUrl; + private String lastSelectedCoverFile; + private String lastSelectedThumbnailFile; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_channel_form, container, false); + + scrollView = root.findViewById(R.id.channel_form_scroll_view); + linkCancel = root.findViewById(R.id.channel_form_cancel_link); + linkShowOptional = root.findViewById(R.id.channel_form_toggle_optional); + buttonSave = root.findViewById(R.id.channel_form_save_button); + + containerOptionalFields = root.findViewById(R.id.channel_form_optional_fields_container); + inputTitle = root.findViewById(R.id.channel_form_input_title); + inputChannelName = root.findViewById(R.id.channel_form_input_channel_name); + inputDeposit = root.findViewById(R.id.channel_form_input_deposit); + inputDescription = root.findViewById(R.id.channel_form_input_description); + inputWebsite = root.findViewById(R.id.channel_form_input_website); + inputEmail = root.findViewById(R.id.channel_form_input_email); + inputTagFilter = root.findViewById(R.id.form_tag_filter_input); + + coverEditArea = root.findViewById(R.id.channel_form_cover_edit_area); + iconContainer = root.findViewById(R.id.channel_form_icon_container); + imageCover = root.findViewById(R.id.channel_form_cover_image); + imageThumbnail = root.findViewById(R.id.channel_form_thumbnail); + inlineBalanceContainer = root.findViewById(R.id.channel_form_inline_balance_container); + inlineBalanceValue = root.findViewById(R.id.channel_form_inline_balance_value); + uploadProgress = root.findViewById(R.id.channel_form_upload_progress); + channelSaveProgress = root.findViewById(R.id.channel_form_save_progress); + + Context context = getContext(); + FlexboxLayoutManager flm1 = new FlexboxLayoutManager(context); + FlexboxLayoutManager flm2 = new FlexboxLayoutManager(context); + FlexboxLayoutManager flm3 = new FlexboxLayoutManager(context); + addedTagsList = root.findViewById(R.id.form_added_tags); + addedTagsList.setLayoutManager(flm1); + suggestedTagsList = root.findViewById(R.id.form_suggested_tags); + suggestedTagsList.setLayoutManager(flm2); + + addedTagsAdapter = new TagListAdapter(new ArrayList<>(), context); + addedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_REMOVE); + addedTagsAdapter.setClickListener(this); + addedTagsList.setAdapter(addedTagsAdapter); + + suggestedTagsAdapter = new TagListAdapter(new ArrayList<>(), getContext()); + suggestedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD); + suggestedTagsAdapter.setClickListener(this); + suggestedTagsList.setAdapter(suggestedTagsAdapter); + + noTagsView = root.findViewById(R.id.form_no_added_tags); + noTagResultsView = root.findViewById(R.id.form_no_tag_results); + + buttonSave = root.findViewById(R.id.channel_form_save_button); + + inputDeposit.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + Helper.setViewVisibility(inlineBalanceContainer, hasFocus ? View.VISIBLE : View.INVISIBLE); + } + }); + linkShowOptional.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (containerOptionalFields.getVisibility() != View.VISIBLE) { + containerOptionalFields.setVisibility(View.VISIBLE); + linkShowOptional.setText(R.string.hide_optional_fields); + scrollView.post(new Runnable() { + @Override + public void run() { + scrollView.fullScroll(NestedScrollView.FOCUS_DOWN); + } + }); + } else { + containerOptionalFields.setVisibility(View.GONE); + linkShowOptional.setText(R.string.show_optional_fields); + } + } + }); + linkCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + clearInputFocus(); + + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).onBackPressed(); + } + } + }); + coverEditArea.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (uploading) { + Snackbar.make(getView(), R.string.wait_for_upload, Snackbar.LENGTH_LONG).show(); + return; + } + + checkPermissionsAndLaunchFilePicker(true); + } + }); + iconContainer.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (uploading) { + Snackbar.make(getView(), R.string.wait_for_upload, Snackbar.LENGTH_LONG).show(); + return; + } + + checkPermissionsAndLaunchFilePicker(false); + } + }); + + buttonSave.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Claim claimToSave = buildChannelClaimToSave(); + validateAndSaveClaim(claimToSave); + } + }); + inputTagFilter.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + String value = Helper.getValue(charSequence); + setFilter(value); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + + return root; + } + + private void checkParams() { + Map params = getParams(); + if (params.containsKey("claim")) { + Claim claim = (Claim) params.get("claim"); + if (claim != null && !claim.equals(this.currentClaim)) { + this.currentClaim = claim; + editFieldsLoaded = false; + } + } + } + + private void updateFieldsFromCurrentClaim() { + if (currentClaim != null && !editFieldsLoaded) { + inputTitle.setText(currentClaim.getTitle()); + inputChannelName.setText(currentClaim.getName()); + inputDeposit.setText(currentClaim.getAmount()); + inputEmail.setText(currentClaim.getEmail()); + inputWebsite.setText(currentClaim.getWebsiteUrl()); + inputDescription.setText(currentClaim.getDescription()); + if (currentClaim.getTagObjects() != null) { + addedTagsAdapter.addTags(currentClaim.getTagObjects()); + } + + Context context = getContext(); + if (context != null) { + if (!Helper.isNullOrEmpty(currentClaim.getCoverUrl())) { + Glide.with(context.getApplicationContext()).load(currentClaim.getCoverUrl()).centerCrop().into(imageCover); + coverUrl = currentClaim.getCoverUrl(); + } + if (!Helper.isNullOrEmpty(currentClaim.getThumbnailUrl())) { + Glide.with(context.getApplicationContext()).load(currentClaim.getThumbnailUrl()).apply(RequestOptions.circleCropTransform()).into(imageThumbnail); + thumbnailUrl = currentClaim.getThumbnailUrl(); + } + } + + inputChannelName.setEnabled(false); + editMode = true; + editFieldsLoaded = true; + } + } + + private void validateAndSaveClaim(Claim claim) { + if (!editMode && Helper.isNullOrEmpty(claim.getName())) { + showError(getString(R.string.please_enter_channel_name)); + return; + } + + String channelName = claim.getName().startsWith("@") ? claim.getName().substring(1) : claim.getName(); + if (!LbryUri.isNameValid(channelName)) { + showError(getString(R.string.channel_name_invalid_characters)); + return; + } + if (!editMode) { + if (Helper.channelExists(channelName)) { + showError(getString(R.string.channel_name_already_created)); + return; + } + } + + String depositString = Helper.getValue(inputDeposit.getText()); + double depositAmount = 0; + try { + depositAmount = Double.valueOf(depositString); + } catch (NumberFormatException ex) { + // pass + showError(getString(R.string.please_enter_valid_deposit)); + return; + } + if (depositAmount == 0) { + String error = getResources().getQuantityString(R.plurals.min_deposit_required, depositAmount == 1 ? 1 : 2, String.valueOf(Helper.MIN_DEPOSIT)); + showError(error); + return; + } + if (Lbry.walletBalance == null || Lbry.walletBalance.getAvailable().doubleValue() < depositAmount) { + showError(getString(R.string.deposit_more_than_balance)); + return; + } + + ChannelCreateUpdateTask task = new ChannelCreateUpdateTask(claim, new BigDecimal(depositString), editMode, channelSaveProgress, new ClaimResultHandler() { + @Override + public void beforeStart() { + preSave(); + } + + @Override + public void onSuccess(Claim claimResult) { + postSave(); + + // Run the logPublish task + if (!BuildConfig.DEBUG) { + LogPublishTask logPublish = new LogPublishTask(claimResult); + logPublish.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + if (!editMode) { + // channel created + Bundle bundle = new Bundle(); + bundle.putString("claim_id", claimResult.getClaimId()); + bundle.putString("claim_name", claimResult.getName()); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_CHANNEL_CREATE, bundle); + } + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.showMessage(R.string.channel_save_successful); + activity.onBackPressed(); + } + } + + @Override + public void onError(Exception error) { + showError(error != null ? error.getMessage() : getString(R.string.channel_save_failed)); + postSave(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void showError(String message) { + Context context = getContext(); + if (context != null) { + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } + } + + public void checkPermissionsAndLaunchFilePicker(boolean isCover) { + Context context = getContext(); + if (MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context)) { + launchCoverSelectPending = false; + launchThumbnailSelectPending = false; + + coverFilePickerActive = isCover; + thumbnailFilePickerActive = !isCover; + launchFilePicker(); + } else { + launchCoverSelectPending = isCover; + launchThumbnailSelectPending = !isCover; + MainActivity.requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, + MainActivity.REQUEST_STORAGE_PERMISSION, + getString(R.string.storage_permission_rationale_images), + context, + true); + } + } + + private void launchFilePicker() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity.startingFilePickerActivity = true; + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("image/*"); + ((MainActivity) context).startActivityForResult( + Intent.createChooser(intent, getString(coverFilePickerActive ? R.string.select_cover : R.string.select_thumbnail)), + MainActivity.REQUEST_FILE_PICKER); + } + } + + public void onFilePickerCancelled() { + coverFilePickerActive = false; + thumbnailFilePickerActive = false; + } + + public void onFilePicked(String filePath) { + if (Helper.isNullOrEmpty(filePath)) { + Snackbar.make(getView(), R.string.undetermined_image_filepath, Snackbar.LENGTH_LONG).setBackgroundTint( + ContextCompat.getColor(getContext(), R.color.red)).show(); + return; + } + + Context context = getContext(); + if (context != null) { + Uri fileUri = Uri.fromFile(new File(filePath)); + if (coverFilePickerActive) { + // cover selected + if (filePath.equalsIgnoreCase(lastSelectedCoverFile)) { + // previous selected cover was uploaded successfully + return; + } + Glide.with(context.getApplicationContext()).load(fileUri).centerCrop().into(imageCover); + } else if (thumbnailFilePickerActive) { + if (filePath.equalsIgnoreCase(lastSelectedThumbnailFile)) { + // previous selected thumbnail was uploaded successfully + return; + } + // thumbnail selected + Glide.with(context.getApplicationContext()).load(fileUri).apply(RequestOptions.circleCropTransform()).into(imageThumbnail); + } + + // Upload the image + uploading = true; + UploadImageTask task = new UploadImageTask(filePath, uploadProgress, new UploadImageTask.UploadThumbnailHandler() { + @Override + public void onSuccess(String url) { + if (coverFilePickerActive) { + coverUrl = url; + lastSelectedCoverFile = filePath; + } else if (thumbnailFilePickerActive) { + thumbnailUrl = url; + lastSelectedThumbnailFile = filePath; + } + + coverFilePickerActive = false; + thumbnailFilePickerActive = false; + uploading = false; + } + + @Override + public void onError(Exception error) { + if (getContext() != null) { + showError(getString(R.string.image_upload_failed)); + } + if (coverFilePickerActive) { + // cover selected + imageCover.setImageResource(R.drawable.default_channel_cover); + } else if (thumbnailFilePickerActive) { + imageThumbnail.setImageDrawable(null); + } + coverFilePickerActive = false; + thumbnailFilePickerActive = false; + uploading = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + coverFilePickerActive = false; + thumbnailFilePickerActive = false; + } + } + + public void onStoragePermissionGranted() { + if (launchCoverSelectPending) { + checkPermissionsAndLaunchFilePicker(true); + } else if (launchThumbnailSelectPending) { + checkPermissionsAndLaunchFilePicker(false); + } + } + public void onStoragePermissionRefused() { + Snackbar.make(getView(), R.string.storage_permission_rationale_images, Snackbar.LENGTH_LONG).setBackgroundTint( + ContextCompat.getColor(getContext(), R.color.red) + ).show(); + } + + @Override + public void onStart() { + super.onStart(); + MainActivity activity = (MainActivity) getContext(); + if (activity != null) { + activity.hideSearchBar(); + activity.showNavigationBackIcon(); + activity.lockDrawer(); + activity.hideFloatingWalletBalance(); + + activity.addFilePickerListener(this); + activity.addWalletBalanceListener(this); + + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(editMode ? R.string.edit_channel : R.string.create_a_channel); + } + } + } + + @Override + public void onPause() { + clearInputFocus(); + super.onPause(); + } + + @Override + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) getContext(); + activity.removeStoragePermissionListener(this); + activity.removeWalletBalanceListener(this); + activity.restoreToggle(); + activity.showFloatingWalletBalance(); + if (!MainActivity.startingFilePickerActivity) { + activity.removeFilePickerListener(this); + activity.removeNavFragment(ChannelFormFragment.class, NavMenuItem.ID_ITEM_CHANNELS); + } + } + super.onStop(); + } + + public void onResume() { + super.onResume(); + checkParams(); + updateFieldsFromCurrentClaim(); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Channel Form", "ChannelForm"); + activity.addStoragePermissionListener(this); + if (editMode) { + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(R.string.edit_channel); + } + } + } + String filterText = Helper.getValue(inputTagFilter.getText()); + updateSuggestedTags(filterText, SUGGESTED_LIMIT, true); + } + + private void checkNoAddedTags() { + Helper.setViewVisibility(noTagsView, addedTagsAdapter == null || addedTagsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + private void checkNoTagResults() { + Helper.setViewVisibility(noTagResultsView, suggestedTagsAdapter == null || suggestedTagsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + public void addTag(Tag tag) { + if (addedTagsAdapter.getTags().contains(tag)) { + Snackbar.make(getView(), getString(R.string.tag_already_added, tag.getName()), Snackbar.LENGTH_LONG).show(); + return; + } + if (addedTagsAdapter.getItemCount() == 5) { + Snackbar.make(getView(), R.string.tag_limit_reached, Snackbar.LENGTH_LONG).show(); + return; + } + + addedTagsAdapter.addTag(tag); + if (suggestedTagsAdapter != null) { + suggestedTagsAdapter.removeTag(tag); + } + updateSuggestedTags(currentFilter, SUGGESTED_LIMIT, false); + + checkNoAddedTags(); + checkNoTagResults(); + } + public void removeTag(Tag tag) { + addedTagsAdapter.removeTag(tag); + updateSuggestedTags(currentFilter, SUGGESTED_LIMIT, false); + checkNoAddedTags(); + checkNoTagResults(); + } + + @Override + public void onWalletBalanceUpdated(WalletBalance walletBalance) { + if (walletBalance != null && inlineBalanceValue != null) { + inlineBalanceValue.setText(Helper.shortCurrencyFormat(walletBalance.getAvailable().doubleValue())); + } + } + + public void setFilter(String filter) { + currentFilter = filter; + updateSuggestedTags(currentFilter, SUGGESTED_LIMIT, true); + } + + private void updateSuggestedTags(String filter, int limit, boolean clearPrevious) { + UpdateSuggestedTagsTask task = new UpdateSuggestedTagsTask( + filter, + limit, + addedTagsAdapter, + suggestedTagsAdapter, + clearPrevious, + false, + new UpdateSuggestedTagsTask.KnownTagsHandler() { + @Override + public void onSuccess(List tags) { + if (suggestedTagsAdapter == null) { + suggestedTagsAdapter = new TagListAdapter(tags, getContext()); + suggestedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD); + suggestedTagsAdapter.setClickListener(ChannelFormFragment.this); + if (suggestedTagsList != null) { + suggestedTagsList.setAdapter(suggestedTagsAdapter); + } + } else { + suggestedTagsAdapter.setTags(tags); + } + + checkNoAddedTags(); + checkNoTagResults(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private Claim buildChannelClaimToSave() { + Claim claim = new Claim(); + String name = Helper.getValue(inputChannelName.getText()); + if (!name.startsWith("@")) { + name = String.format("@%s", name); + } + claim.setName(name); + + if (currentClaim != null) { + claim.setClaimId(currentClaim.getClaimId()); + } + + Claim.ChannelMetadata metadata = new Claim.ChannelMetadata(); + metadata.setTitle(Helper.getValue(inputTitle.getText())); + metadata.setDescription(Helper.getValue(inputDescription.getText())); + metadata.setWebsiteUrl(Helper.getValue(inputWebsite.getText())); + metadata.setEmail(Helper.getValue(inputEmail.getText())); + + Claim.Resource cover = new Claim.Resource(); + cover.setUrl(coverUrl == null ? "" : coverUrl); + Claim.Resource thumbnail = new Claim.Resource(); + thumbnail.setUrl(thumbnailUrl == null ? "" : thumbnailUrl); + metadata.setThumbnail(thumbnail); + metadata.setCover(cover); + + List addedTags = addedTagsAdapter != null ? new ArrayList<>(addedTagsAdapter.getTags()) : new ArrayList<>(); + metadata.setTags(Helper.getTagsForTagObjects(addedTags)); + + claim.setValue(metadata); + return claim; + } + + private void preSave() { + saveInProgress = true; + Helper.setViewVisibility(linkShowOptional, View.GONE); + Helper.setViewEnabled(linkCancel, false); + Helper.setViewEnabled(buttonSave, false); + } + + private void postSave() { + Helper.setViewVisibility(linkShowOptional, View.VISIBLE); + Helper.setViewEnabled(linkCancel, true); + Helper.setViewEnabled(buttonSave, true); + + clearInputFocus(); + + saveInProgress = false; + } + + public void clearInputFocus() { + inputChannelName.clearFocus(); + inputDeposit.clearFocus(); + inputWebsite.clearFocus(); + inputEmail.clearFocus(); + inputDescription.clearFocus(); + inputTitle.clearFocus(); + inputTagFilter.clearFocus(); + } + + @Override + public void onTagClicked(Tag tag, int customizeMode) { + if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_ADD) { + addTag(tag); + } else if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_REMOVE) { + removeTag(tag); + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java new file mode 100644 index 00000000..3299aec7 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java @@ -0,0 +1,531 @@ +package io.lbry.browser.ui.channel; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.dialog.SendTipDialogFragment; +import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.listener.FetchChannelsListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.ClaimCacheKey; +import io.lbry.browser.model.UrlSuggestion; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.tasks.claim.AbandonChannelTask; +import io.lbry.browser.tasks.claim.AbandonHandler; +import io.lbry.browser.tasks.lbryinc.ChannelSubscribeTask; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ResolveTask; +import io.lbry.browser.tasks.lbryinc.FetchStatCountTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.ui.controls.SolidIconView; +import io.lbry.browser.ui.findcontent.FollowingFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; +import lombok.SneakyThrows; + +public class ChannelFragment extends BaseFragment implements FetchChannelsListener { + private Claim claim; + private boolean subscribing; + private String url; + + private View layoutResolving; + private View layoutDisplayArea; + private ImageView imageCover; + private ImageView imageThumbnail; + private View noThumbnailView; + private TextView textAlpha; + private TextView textTitle; + private TextView textFollowerCount; + private TabLayout tabLayout; + private ViewPager2 tabPager; + + private View buttonEdit; + private View buttonDelete; + private View buttonShare; + private View buttonTip; + private View buttonFollowUnfollow; + private int subCount; + private SolidIconView iconFollowUnfollow; + private View layoutNothingAtLocation; + private View layoutLoadingState; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_channel, container, false); + + layoutLoadingState = root.findViewById(R.id.channel_view_loading_state); + layoutNothingAtLocation = root.findViewById(R.id.container_nothing_at_location); + layoutDisplayArea = root.findViewById(R.id.channel_view_claim_display_area); + layoutResolving = root.findViewById(R.id.channel_view_loading_container); + + imageCover = root.findViewById(R.id.channel_view_cover_image); + imageThumbnail = root.findViewById(R.id.channel_view_thumbnail); + noThumbnailView = root.findViewById(R.id.channel_view_no_thumbnail); + textAlpha = root.findViewById(R.id.channel_view_icon_alpha); + textTitle = root.findViewById(R.id.channel_view_title); + textFollowerCount = root.findViewById(R.id.channel_view_follower_count); + + buttonEdit = root.findViewById(R.id.channel_view_edit); + buttonDelete = root.findViewById(R.id.channel_view_delete); + buttonShare = root.findViewById(R.id.channel_view_share); + buttonTip = root.findViewById(R.id.channel_view_tip); + buttonFollowUnfollow = root.findViewById(R.id.channel_view_follow_unfollow); + iconFollowUnfollow = root.findViewById(R.id.channel_view_icon_follow_unfollow); + + tabPager = root.findViewById(R.id.channel_view_pager); + tabLayout = root.findViewById(R.id.channel_view_tabs); + tabPager.setSaveEnabled(false); + + buttonEdit.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).openChannelForm(claim); + } + } + } + }); + buttonDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null) { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()). + setTitle(R.string.delete_channel). + setMessage(R.string.confirm_delete_channel) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + deleteCurrentClaim(); + } + }).setNegativeButton(R.string.no, null); + builder.show(); + } + } + }); + + buttonShare.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null) { + try { + String shareUrl = LbryUri.parse( + !Helper.isNullOrEmpty(claim.getCanonicalUrl()) ? claim.getCanonicalUrl() : + (!Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl())).toTvString(); + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.setType("text/plain"); + shareIntent.putExtra(Intent.EXTRA_TEXT, shareUrl); + + MainActivity.startingShareActivity = true; + Intent shareUrlIntent = Intent.createChooser(shareIntent, getString(R.string.share_lbry_content)); + shareUrlIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(shareUrlIntent); + } catch (LbryUriException ex) { + // pass + } + } + } + }); + + buttonTip.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(getView(), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + if (claim != null) { + SendTipDialogFragment dialog = SendTipDialogFragment.newInstance(); + dialog.setClaim(claim); + dialog.setListener(new SendTipDialogFragment.SendTipListener() { + @Override + public void onTipSent(BigDecimal amount) { + double sentAmount = amount.doubleValue(); + String message = getResources().getQuantityString( + R.plurals.you_sent_a_tip, sentAmount == 1.0 ? 1 : 2, + new DecimalFormat("#,###.##").format(sentAmount)); + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show(); + } + }); + Context context = getContext(); + if (context instanceof MainActivity) { + dialog.show(((MainActivity) context).getSupportFragmentManager(), SendTipDialogFragment.TAG); + } + } + } + }); + + buttonFollowUnfollow.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null) { + if (subscribing) { + return; + } + + subscribing = true; + boolean isFollowing = Lbryio.isFollowing(claim); + Subscription subscription = Subscription.fromClaim(claim); + buttonFollowUnfollow.setEnabled(false); + new ChannelSubscribeTask(getContext(), claim.getClaimId(), subscription, isFollowing, new ChannelSubscribeTask.ChannelSubscribeHandler() { + @Override + public void onSuccess() { + if (isFollowing) { + Lbryio.removeSubscription(subscription); + Lbryio.removeCachedResolvedSubscription(claim); + } else { + Lbryio.addSubscription(subscription); + Lbryio.addCachedResolvedSubscription(claim); + } + buttonFollowUnfollow.setEnabled(true); + subscribing = false; + checkIsFollowing(); + FollowingFragment.resetClaimSearchContent = true; + + if (Lbry.SDK_READY) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).saveSharedUserState(); + } + } + } + + @Override + public void onError(Exception exception) { + buttonFollowUnfollow.setEnabled(true); + subscribing = false; + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + }); + + return root; + } + + private void deleteCurrentClaim() { + if (claim != null) { + Helper.setViewVisibility(layoutDisplayArea, View.GONE); + Helper.setViewVisibility(layoutLoadingState, View.VISIBLE); + AbandonChannelTask task = new AbandonChannelTask(Arrays.asList(claim.getClaimId()), layoutResolving, new AbandonHandler() { + @Override + public void onComplete(List successfulClaimIds, List failedClaimIds, List errors) { + Context context = getContext(); + if (context instanceof MainActivity) { + if (failedClaimIds.size() == 0) { + MainActivity activity = (MainActivity) context; + activity.showMessage(R.string.channel_deleted); + activity.onBackPressed(); + } else { + View root = getView(); + if (root != null) { + Snackbar.make(root, R.string.channel_failed_delete, Toast.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } + } + } + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + private void checkIsFollowing() { + if (claim != null) { + boolean isFollowing = Lbryio.isFollowing(claim); + if (iconFollowUnfollow != null) { + iconFollowUnfollow.setText(isFollowing ? R.string.fa_heart_broken : R.string.fa_heart); + Context context = getContext(); + if (context != null) { + iconFollowUnfollow.setTextColor(ContextCompat.getColor(context, isFollowing ? R.color.foreground : R.color.red)); + } + } + } + } + + public void onChannelsFetched(List channels) { + checkOwnChannel(); + } + + private void checkOwnChannel() { + if (claim != null) { + boolean isOwnChannel = Lbry.ownChannels.contains(claim); + Helper.setViewVisibility(buttonEdit, isOwnChannel ? View.VISIBLE : View.GONE); + Helper.setViewVisibility(buttonDelete, isOwnChannel ? View.VISIBLE : View.GONE); + } + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + Map params = getParams(); + String url = params != null && params.containsKey("url") ? (String) params.get("url") : null; + Helper.setWunderbarValue(url, context); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.addFetchChannelsListener(this); + LbryAnalytics.setCurrentScreen(activity, "Channel", "Channel"); + } + + checkParams(); + checkOwnChannel(); + } + + public void onPause() { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).removeFetchChannelsListener(this); + } + super.onPause(); + } + + private void checkParams() { + boolean updateRequired = false; + Map params = getParams(); + + if (params.containsKey("claim")) { + Claim claim = (Claim) params.get("claim"); + if (claim != null && !claim.equals(this.claim)) { + this.claim = claim; + updateRequired = true; + } + } + if (params.containsKey("url")) { + String newUrl = params.get("url").toString(); + if (claim == null || !newUrl.equalsIgnoreCase(url)) { + this.claim = null; + this.url = newUrl; + updateRequired = true; + } + } + + if (updateRequired) { + resetSubCount(); + if (!Helper.isNullOrEmpty(url)) { + // check if the claim is already cached + ClaimCacheKey key = new ClaimCacheKey(); + key.setUrl(url); + if (Lbry.claimCache.containsKey(key)) { + claim = Lbry.claimCache.get(key); + } else { + resolveUrl(); + } + } else if (claim == null) { + // nothing at this location + renderNothingAtLocation(); + } + } + + if (!Helper.isNullOrEmpty(url)) { + Helper.saveUrlHistory(url, claim != null ? claim.getTitle() : null, UrlSuggestion.TYPE_CHANNEL); + } + + if (claim != null) { + renderClaim(); + } + } + + private void resolveUrl() { + Helper.setViewVisibility(layoutDisplayArea, View.INVISIBLE); + Helper.setViewVisibility(layoutLoadingState, View.VISIBLE); + ResolveTask task = new ResolveTask(url, Lbry.LBRY_TV_CONNECTION_STRING, layoutResolving, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + if (claims.size() > 0 && !Helper.isNullOrEmpty(claims.get(0).getClaimId())) { + claim = claims.get(0); + if (!Helper.isNullOrEmpty(url)) { + Helper.saveUrlHistory(url, claim.getTitle(), UrlSuggestion.TYPE_CHANNEL); + } + + renderClaim(); + checkOwnChannel(); + } else { + renderNothingAtLocation(); + } + } + + @Override + public void onError(Exception error) { + renderNothingAtLocation(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void renderNothingAtLocation() { + layoutLoadingState.setVisibility(View.VISIBLE); + layoutNothingAtLocation.setVisibility(View.VISIBLE); + layoutDisplayArea.setVisibility(View.INVISIBLE); + } + + public void setParams(Map params) { + super.setParams(params); + if (getView() != null) { + checkParams(); + } + } + + private void renderClaim() { + if (claim == null) { + renderNothingAtLocation(); + return; + } + + loadSubCount(); + checkIsFollowing(); + layoutLoadingState.setVisibility(View.GONE); + layoutDisplayArea.setVisibility(View.VISIBLE); + + String thumbnailUrl = claim.getThumbnailUrl(); + String coverUrl = claim.getCoverUrl(); + textTitle.setText(Helper.isNullOrEmpty(claim.getTitle()) ? claim.getName() : claim.getTitle()); + + Context context = getContext(); + if (context != null && !Helper.isNullOrEmpty(coverUrl)) { + Glide.with(context.getApplicationContext()).load(coverUrl).centerCrop().into(imageCover); + } + if (context != null && !Helper.isNullOrEmpty(thumbnailUrl)) { + Glide.with(context.getApplicationContext()).load(thumbnailUrl).apply(RequestOptions.circleCropTransform()).into(imageThumbnail); + noThumbnailView.setVisibility(View.GONE); + } else { + imageThumbnail.setVisibility(View.GONE); + + int bgColor = Helper.generateRandomColorForValue(claim.getClaimId()); + Helper.setIconViewBackgroundColor(noThumbnailView, bgColor, false, getContext()); + noThumbnailView.setVisibility(View.VISIBLE); + if (claim.getName() != null) { + textAlpha.setText(claim.getName().substring(1, 2)); + } + } + + try { + if (tabPager.getAdapter() == null && context instanceof MainActivity) { + tabPager.setAdapter(new ChannelPagerAdapter(claim, (MainActivity) context)); + } + } catch (IllegalStateException ex) { + // TODO: Fix why this is happening + // pass + } + new TabLayoutMediator(tabLayout, tabPager, new TabLayoutMediator.TabConfigurationStrategy() { + @Override + public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) { + tab.setText(position == 0 ? R.string.content : R.string.about); + } + }).attach(); + } + + private void resetSubCount() { + subCount = -1; + Helper.setViewText(textFollowerCount, null); + Helper.setViewVisibility(textFollowerCount, View.INVISIBLE); + } + + private void loadSubCount() { + if (claim != null) { + FetchStatCountTask task = new FetchStatCountTask( + FetchStatCountTask.STAT_SUB_COUNT, claim.getClaimId(), null, new FetchStatCountTask.FetchStatCountHandler() { + @Override + public void onSuccess(int count) { + try { + String displayText = getResources().getQuantityString(R.plurals.follower_count, count, NumberFormat.getInstance().format(count)); + Helper.setViewText(textFollowerCount, displayText); + Helper.setViewVisibility(textFollowerCount, View.VISIBLE); + } catch (IllegalStateException ex) { + // pass + } + } + + @Override + public void onError(Exception error) { + // pass + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + private static class ChannelPagerAdapter extends FragmentStateAdapter { + private Claim channelClaim; + public ChannelPagerAdapter(Claim channelClaim, FragmentActivity activity) { + super(activity); + this.channelClaim = channelClaim; + } + + @SneakyThrows + @Override + public Fragment createFragment(int position) { + switch (position) { + case 0: + ChannelContentFragment contentFragment = ChannelContentFragment.class.newInstance(); + if (channelClaim != null) { + contentFragment.setChannelId(channelClaim.getClaimId()); + } + return contentFragment; + + case 1: + ChannelAboutFragment aboutFragment = ChannelAboutFragment.class.newInstance(); + try { + Claim.ChannelMetadata metadata = (Claim.ChannelMetadata) channelClaim.getValue(); + if (metadata != null) { + aboutFragment.setDescription(metadata.getDescription()); + aboutFragment.setEmail(metadata.getEmail()); + aboutFragment.setWebsite(metadata.getWebsiteUrl()); + } + } catch (ClassCastException ex) { + // pass + } + return aboutFragment; + } + + return null; + } + + public long getItemId(int position) { + return String.format("%s-%d", channelClaim.getClaimId(), position).hashCode(); + } + + @Override + public int getItemCount() { + return 2; + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelManagerFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelManagerFragment.java new file mode 100644 index 00000000..4f2454e2 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelManagerFragment.java @@ -0,0 +1,321 @@ +package io.lbry.browser.ui.channel; + +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ProgressBar; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.view.ActionMode; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.ClaimListAdapter; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.listener.SelectionModeListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.NavMenuItem; +import io.lbry.browser.tasks.claim.AbandonChannelTask; +import io.lbry.browser.tasks.claim.AbandonHandler; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimListTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; + +public class ChannelManagerFragment extends BaseFragment implements ActionMode.Callback, SelectionModeListener, SdkStatusListener { + + private Button buttonNewChannel; + private FloatingActionButton fabNewChannel; + private ActionMode actionMode; + private View emptyView; + private View layoutSdkInitializing; + private ProgressBar loading; + private ProgressBar bigLoading; + private RecyclerView channelList; + private ClaimListAdapter adapter; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_channel_manager, container, false); + + buttonNewChannel = root.findViewById(R.id.channel_manager_create_button); + fabNewChannel = root.findViewById(R.id.channel_manager_fab_new_channel); + buttonNewChannel.setOnClickListener(newChannelClickListener); + fabNewChannel.setOnClickListener(newChannelClickListener); + + emptyView = root.findViewById(R.id.channel_manager_empty_container); + layoutSdkInitializing = root.findViewById(R.id.container_sdk_initializing); + channelList = root.findViewById(R.id.channel_manager_list); + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + channelList.setLayoutManager(llm); + loading = root.findViewById(R.id.channel_manager_list_loading); + bigLoading = root.findViewById(R.id.channel_manager_list_big_loading); + + layoutSdkInitializing.setVisibility(Lbry.SDK_READY ? View.GONE : View.VISIBLE); + + return root; + } + + private View.OnClickListener newChannelClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).openChannelForm(null); + } + } + }; + + @Override + public void onStart() { + super.onStart(); + Context context = getContext(); + if (context != null) { + MainActivity activity = (MainActivity) context; + activity.hideFloatingWalletBalance(); + } + } + + @Override + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.showFloatingWalletBalance(); + } + super.onStop(); + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.setWunderbarValue(null); + LbryAnalytics.setCurrentScreen(activity, "Channel Manager", "ChannelManager"); + } + + if (!Lbry.SDK_READY) { + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.addSdkStatusListener(this); + } + } else { + onSdkReady(); + } + } + + public void onSdkReady() { + Helper.setViewVisibility(layoutSdkInitializing, View.GONE); + Helper.setViewVisibility(fabNewChannel, View.VISIBLE); + if (adapter != null && channelList != null) { + channelList.setAdapter(adapter); + } + fetchChannels(); + } + + public View getLoading() { + return (adapter == null || adapter.getItemCount() == 0) ? bigLoading : loading; + } + + private void checkNoChannels() { + Helper.setViewVisibility(emptyView, adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + + private void fetchChannels() { + Helper.setViewVisibility(emptyView, View.GONE); + ClaimListTask task = new ClaimListTask(Claim.TYPE_CHANNEL, getLoading(), new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + Lbry.ownChannels = Helper.filterDeletedClaims(new ArrayList<>(claims)); + Context context = getContext(); + if (adapter == null) { + adapter = new ClaimListAdapter(claims, context); + adapter.setCanEnterSelectionMode(true); + adapter.setSelectionModeListener(ChannelManagerFragment.this); + adapter.setListener(new ClaimListAdapter.ClaimListItemListener() { + @Override + public void onClaimClicked(Claim claim) { + if (context instanceof MainActivity) { + ((MainActivity) context).openChannelClaim(claim); + } + } + }); + if (channelList != null) { + channelList.setAdapter(adapter); + } + } else { + adapter.setItems(claims); + } + + checkNoChannels(); + } + + @Override + public void onError(Exception error) { + checkNoChannels(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void onEnterSelectionMode() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.startSupportActionMode(this); + } + } + public void onItemSelectionToggled() { + if (actionMode != null) { + actionMode.setTitle(String.valueOf(adapter.getSelectedCount())); + actionMode.invalidate(); + } + } + public void onExitSelectionMode() { + if (actionMode != null) { + actionMode.finish(); + } + } + + @Override + public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { + this.actionMode = actionMode; + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (!activity.isDarkMode()) { + activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + } + } + + actionMode.getMenuInflater().inflate(R.menu.menu_claim_list, menu); + return true; + } + @Override + public void onDestroyActionMode(ActionMode actionMode) { + if (adapter != null) { + adapter.clearSelectedItems(); + adapter.setInSelectionMode(false); + adapter.notifyDataSetChanged(); + } + Context context = getContext(); + if (context != null) { + MainActivity activity = (MainActivity) context; + if (!activity.isDarkMode()) { + activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + } + this.actionMode = null; + } + + @Override + public boolean onPrepareActionMode(androidx.appcompat.view.ActionMode actionMode, Menu menu) { + int selectionCount = adapter != null ? adapter.getSelectedCount() : 0; + menu.findItem(R.id.action_edit).setVisible(selectionCount == 1); + return true; + } + + @Override + public boolean onActionItemClicked(androidx.appcompat.view.ActionMode actionMode, MenuItem menuItem) { + if (R.id.action_edit == menuItem.getItemId()) { + if (adapter != null && adapter.getSelectedCount() > 0) { + Claim claim = adapter.getSelectedItems().get(0); + // start channel editor with the claim + Context context = getContext(); + if (context instanceof MainActivity) { + Map params = new HashMap<>(); + params.put("claim", claim); + ((MainActivity) context).openFragment(ChannelFormFragment.class, true, NavMenuItem.ID_ITEM_CHANNELS, params); + } + + actionMode.finish(); + return true; + } + } + if (R.id.action_delete == menuItem.getItemId()) { + if (adapter != null && adapter.getSelectedCount() > 0) { + final List selectedClaims = new ArrayList<>(adapter.getSelectedItems()); + String message = getResources().getQuantityString(R.plurals.confirm_delete_channels, selectedClaims.size()); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()). + setTitle(R.string.delete_selection). + setMessage(message) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + handleDeleteSelectedClaims(selectedClaims); + } + }).setNegativeButton(R.string.no, null); + builder.show(); + return true; + } + } + + return false; + } + + private void handleDeleteSelectedClaims(List selectedClaims) { + List claimIds = new ArrayList<>(); + + for (Claim claim : selectedClaims) { + claimIds.add(claim.getClaimId()); + } + + if (actionMode != null) { + actionMode.finish(); + } + + Helper.setViewVisibility(channelList, View.INVISIBLE); + Helper.setViewVisibility(fabNewChannel, View.INVISIBLE); + AbandonChannelTask task = new AbandonChannelTask(claimIds, bigLoading, new AbandonHandler() { + @Override + public void onComplete(List successfulClaimIds, List failedClaimIds, List errors) { + View root = getView(); + if (root != null) { + if (failedClaimIds.size() > 0) { + Snackbar.make(root, R.string.one_or_more_channels_failed_abandon, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } else if (successfulClaimIds.size() == claimIds.size()) { + try { + String message = getResources().getQuantityString(R.plurals.channels_deleted, successfulClaimIds.size()); + Snackbar.make(root, message, Snackbar.LENGTH_LONG).show(); + } catch (IllegalStateException ex) { + // pass + } + } + } + + Lbry.abandonedClaimIds.addAll(successfulClaimIds); + if (adapter != null) { + adapter.setItems(Helper.filterDeletedClaims(adapter.getItems())); + } + + Helper.setViewVisibility(channelList, View.VISIBLE); + Helper.setViewVisibility(fabNewChannel, View.VISIBLE); + checkNoChannels(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/controls/SolidIconView.java b/app/src/main/java/io/lbry/browser/ui/controls/SolidIconView.java new file mode 100644 index 00000000..e9de291b --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/controls/SolidIconView.java @@ -0,0 +1,29 @@ +package io.lbry.browser.ui.controls; + +import android.content.Context; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.view.Gravity; + +import androidx.appcompat.widget.AppCompatTextView; + +public class SolidIconView extends AppCompatTextView { + private Context context; + + public SolidIconView(Context context) { + super(context); + this.context = context; + init(); + } + + public SolidIconView(Context context, AttributeSet attrs) { + super(context, attrs); + this.context = context; + init(); + } + + private void init() { + setGravity(Gravity.CENTER); + setTypeface(Typeface.createFromAsset(context.getAssets(), "font_awesome_5_free_solid.otf")); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/findcontent/AllContentFragment.java b/app/src/main/java/io/lbry/browser/ui/findcontent/AllContentFragment.java new file mode 100644 index 00000000..9eb90191 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/findcontent/AllContentFragment.java @@ -0,0 +1,517 @@ +package io.lbry.browser.ui.findcontent; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.snackbar.Snackbar; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.ClaimListAdapter; +import io.lbry.browser.dialog.ContentFromDialogFragment; +import io.lbry.browser.dialog.ContentScopeDialogFragment; +import io.lbry.browser.dialog.ContentSortDialogFragment; +import io.lbry.browser.dialog.CustomizeTagsDialogFragment; +import io.lbry.browser.listener.DownloadActionListener; +import io.lbry.browser.listener.TagListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.model.Tag; +import io.lbry.browser.tasks.claim.ClaimSearchResultHandler; +import io.lbry.browser.tasks.claim.ClaimSearchTask; +import io.lbry.browser.tasks.FollowUnfollowTagTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.Predefined; +import lombok.Getter; + +// TODO: Similar code to FollowingFragment and Channel page fragment. Probably make common operations (sorting/filtering) into a control +public class AllContentFragment extends BaseFragment implements DownloadActionListener, SharedPreferences.OnSharedPreferenceChangeListener { + + @Getter + private boolean singleTagView; + private List tags; + private View layoutFilterContainer; + private View customizeLink; + private View sortLink; + private View contentFromLink; + private View scopeLink; + private TextView titleView; + private TextView sortLinkText; + private TextView contentFromLinkText; + private TextView scopeLinkText; + private RecyclerView contentList; + private int currentSortBy; + private int currentContentFrom; + @Getter + private int currentContentScope; + private String contentReleaseTime; + private List contentSortOrder; + private View fromPrefix; + private View forPrefix; + private View contentLoading; + private View bigContentLoading; + private View noContentView; + private ClaimListAdapter contentListAdapter; + private boolean contentClaimSearchLoading; + private boolean contentHasReachedEnd; + private int currentClaimSearchPage; + private ClaimSearchTask contentClaimSearchTask; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_all_content, container, false); + + // All content page is sorted by trending by default, past week if sort is top + currentSortBy = ContentSortDialogFragment.ITEM_SORT_BY_TRENDING; + currentContentFrom = ContentFromDialogFragment.ITEM_FROM_PAST_WEEK; + + layoutFilterContainer = root.findViewById(R.id.all_content_filter_container); + titleView = root.findViewById(R.id.all_content_page_title); + sortLink = root.findViewById(R.id.all_content_sort_link); + contentFromLink = root.findViewById(R.id.all_content_time_link); + scopeLink = root.findViewById(R.id.all_content_scope_link); + customizeLink = root.findViewById(R.id.all_content_customize_link); + fromPrefix = root.findViewById(R.id.all_content_from_prefix); + forPrefix = root.findViewById(R.id.all_content_for_prefix); + + sortLinkText = root.findViewById(R.id.all_content_sort_link_text); + contentFromLinkText = root.findViewById(R.id.all_content_time_link_text); + scopeLinkText = root.findViewById(R.id.all_content_scope_link_text); + + bigContentLoading = root.findViewById(R.id.all_content_main_progress); + contentLoading = root.findViewById(R.id.all_content_load_progress); + noContentView = root.findViewById(R.id.all_content_no_claim_search_content); + + contentList = root.findViewById(R.id.all_content_list); + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + contentList.setLayoutManager(llm); + contentList.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + if (contentClaimSearchLoading) { + return; + } + + LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager(); + if (lm != null) { + int visibleItemCount = lm.getChildCount(); + int totalItemCount = lm.getItemCount(); + int pastVisibleItems = lm.findFirstVisibleItemPosition(); + if (pastVisibleItems + visibleItemCount >= totalItemCount) { + if (!contentHasReachedEnd) { + // load more + currentClaimSearchPage++; + fetchClaimSearchContent(); + } + } + } + } + }); + + sortLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ContentSortDialogFragment dialog = ContentSortDialogFragment.newInstance(); + dialog.setCurrentSortByItem(currentSortBy); + dialog.setSortByListener(new ContentSortDialogFragment.SortByListener() { + @Override + public void onSortByItemSelected(int sortBy) { + onSortByChanged(sortBy); + } + }); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + dialog.show(activity.getSupportFragmentManager(), ContentSortDialogFragment.TAG); + } + } + }); + scopeLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ContentScopeDialogFragment dialog = ContentScopeDialogFragment.newInstance(); + dialog.setCurrentScopeItem(currentContentScope); + dialog.setContentScopeListener(new ContentScopeDialogFragment.ContentScopeListener() { + @Override + public void onContentScopeItemSelected(int scopeItem) { + onContentScopeChanged(scopeItem); + } + }); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + dialog.show(activity.getSupportFragmentManager(), ContentScopeDialogFragment.TAG); + } + } + }); + contentFromLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ContentFromDialogFragment dialog = ContentFromDialogFragment.newInstance(); + dialog.setCurrentFromItem(currentContentFrom); + dialog.setContentFromListener(new ContentFromDialogFragment.ContentFromListener() { + @Override + public void onContentFromItemSelected(int contentFromItem) { + onContentFromChanged(contentFromItem); + } + }); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + dialog.show(activity.getSupportFragmentManager(), ContentFromDialogFragment.TAG); + } + } + }); + customizeLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showCustomizeTagsDialog(); + } + }); + + checkParams(false); + return root; + } + + public void setParams(Map params) { + super.setParams(params); + if (getView() != null) { + checkParams(true); + } + } + + private void checkParams(boolean reload) { + Map params = getParams(); + if (params != null && params.containsKey("singleTag")) { + String tagName = params.get("singleTag").toString(); + singleTagView = true; + tags = Arrays.asList(tagName); + titleView.setText(Helper.capitalize(tagName)); + Helper.setViewVisibility(customizeLink, View.GONE); + } else { + singleTagView = false; + // default to followed Tags scope if any tags are followed + tags = Helper.getTagsForTagObjects(Lbry.followedTags); + if (tags.size() > 0) { + currentContentScope = ContentScopeDialogFragment.ITEM_TAGS; + Helper.setViewVisibility(customizeLink, View.VISIBLE); + } + titleView.setText(getString(R.string.all_content)); + } + + Helper.setViewVisibility(forPrefix, singleTagView ? View.GONE : View.VISIBLE); + Helper.setViewVisibility(scopeLink, singleTagView ? View.GONE : View.VISIBLE); + + if (reload) { + fetchClaimSearchContent(true); + } + } + + private void onContentFromChanged(int contentFrom) { + currentContentFrom = contentFrom; + + // rebuild options and search + updateContentFromLinkText(); + contentReleaseTime = Helper.buildReleaseTime(currentContentFrom); + fetchClaimSearchContent(true); + } + + private void onContentScopeChanged(int contentScope) { + currentContentScope = contentScope; + + // rebuild options and search + updateContentScopeLinkText(); + boolean isTagScope = currentContentScope == ContentScopeDialogFragment.ITEM_TAGS; + if (isTagScope) { + tags = Helper.getTagsForTagObjects(Lbry.followedTags); + // Update tags list with the user's followed tags + if (tags == null || tags.size() == 0) { + Snackbar.make(getView(), R.string.customize_tags_hint, Snackbar.LENGTH_LONG).setAction(R.string.customize, new View.OnClickListener() { + @Override + public void onClick(View view) { + // show customize + showCustomizeTagsDialog(); + } + }).show(); + } + } + Helper.setViewVisibility(customizeLink, isTagScope ? View.VISIBLE : View.GONE); + fetchClaimSearchContent(true); + } + + private void showCustomizeTagsDialog() { + CustomizeTagsDialogFragment dialog = CustomizeTagsDialogFragment.newInstance(); + dialog.setListener(new TagListener() { + @Override + public void onTagAdded(Tag tag) { + // heavy-lifting + // save to local, save to wallet and then sync + FollowUnfollowTagTask task = new FollowUnfollowTagTask(tag, false, getContext(), followUnfollowHandler); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + @Override + public void onTagRemoved(Tag tag) { + // heavy-lifting + // save to local, save to wallet and then sync + FollowUnfollowTagTask task = new FollowUnfollowTagTask(tag, true, getContext(), followUnfollowHandler); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + dialog.show(activity.getSupportFragmentManager(), CustomizeTagsDialogFragment.TAG); + } + } + + private FollowUnfollowTagTask.FollowUnfollowTagHandler followUnfollowHandler = new FollowUnfollowTagTask.FollowUnfollowTagHandler() { + @Override + public void onSuccess(Tag tag, boolean unfollowing) { + if (tags != null) { + if (unfollowing) { + tags.remove(tag.getLowercaseName()); + } else { + tags.add(tag.getLowercaseName()); + } + fetchClaimSearchContent(true); + } + + Bundle bundle = new Bundle(); + bundle.putString("tag", tag.getLowercaseName()); + LbryAnalytics.logEvent(unfollowing ? LbryAnalytics.EVENT_TAG_UNFOLLOW : LbryAnalytics.EVENT_TAG_FOLLOW, bundle); + + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).saveSharedUserState(); + } + } + + @Override + public void onError(Exception error) { + // pass + } + }; + + private void onSortByChanged(int sortBy) { + currentSortBy = sortBy; + + // rebuild options and search + Helper.setViewVisibility(fromPrefix, currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ? View.VISIBLE : View.GONE); + Helper.setViewVisibility(contentFromLink, currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ? View.VISIBLE : View.GONE); + currentContentFrom = currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ? + (currentContentFrom == 0 ? ContentFromDialogFragment.ITEM_FROM_PAST_WEEK : currentContentFrom) : 0; + + updateSortByLinkText(); + contentSortOrder = Helper.buildContentSortOrder(currentSortBy); + contentReleaseTime = Helper.buildReleaseTime(currentContentFrom); + fetchClaimSearchContent(true); + } + + private void updateSortByLinkText() { + int stringResourceId = -1; + switch (currentSortBy) { + case ContentSortDialogFragment.ITEM_SORT_BY_NEW: default: stringResourceId = R.string.new_text; break; + case ContentSortDialogFragment.ITEM_SORT_BY_TOP: stringResourceId = R.string.top; break; + case ContentSortDialogFragment.ITEM_SORT_BY_TRENDING: stringResourceId = R.string.trending; break; + } + + Helper.setViewText(sortLinkText, stringResourceId); + } + + private void updateContentScopeLinkText() { + int stringResourceId = -1; + switch (currentContentScope) { + case ContentScopeDialogFragment.ITEM_EVERYONE: default: stringResourceId = R.string.everyone; break; + case ContentScopeDialogFragment.ITEM_TAGS: stringResourceId = R.string.tags; break; + } + + Helper.setViewText(scopeLinkText, stringResourceId); + } + + private void updateContentFromLinkText() { + int stringResourceId = -1; + switch (currentContentFrom) { + case ContentFromDialogFragment.ITEM_FROM_PAST_24_HOURS: stringResourceId = R.string.past_24_hours; break; + case ContentFromDialogFragment.ITEM_FROM_PAST_WEEK: default: stringResourceId = R.string.past_week; break; + case ContentFromDialogFragment.ITEM_FROM_PAST_MONTH: stringResourceId = R.string.past_month; break; + case ContentFromDialogFragment.ITEM_FROM_PAST_YEAR: stringResourceId = R.string.past_year; break; + case ContentFromDialogFragment.ITEM_FROM_ALL_TIME: stringResourceId = R.string.all_time; break; + } + + Helper.setViewText(contentFromLinkText, stringResourceId); + } + + public void onResume() { + super.onResume(); + Helper.setWunderbarValue(null, getContext()); + checkParams(false); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (singleTagView) { + LbryAnalytics.setCurrentScreen(activity, "Tag", "Tag"); + } else { + LbryAnalytics.setCurrentScreen(activity, "All Content", "AllContent"); + } + activity.addDownloadActionListener(this); + } + + PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this); + updateContentFromLinkText(); + updateContentScopeLinkText(); + updateSortByLinkText(); + fetchClaimSearchContent(); + } + + public void onPause() { + Context context = getContext(); + if (context != null) { + ((MainActivity) context).removeDownloadActionListener(this); + } + PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); + } + + private Map buildContentOptions() { + Context context = getContext(); + boolean canShowMatureContent = false; + if (context != null) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + } + + return Lbry.buildClaimSearchOptions( + null, + (currentContentScope == ContentScopeDialogFragment.ITEM_EVERYONE) ? null : tags, + canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS), + null, + null, + getContentSortOrder(), + contentReleaseTime, + currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage, + Helper.CONTENT_PAGE_SIZE); + } + + private List getContentSortOrder() { + if (contentSortOrder == null) { + return Arrays.asList(Claim.ORDER_BY_TRENDING_GROUP, Claim.ORDER_BY_TRENDING_MIXED); + } + return contentSortOrder; + } + + private View getLoadingView() { + return (contentListAdapter == null || contentListAdapter.getItemCount() == 0) ? bigContentLoading : contentLoading; + } + + private void fetchClaimSearchContent() { + fetchClaimSearchContent(false); + } + + public void fetchClaimSearchContent(boolean reset) { + if (reset && contentListAdapter != null) { + contentListAdapter.clearItems(); + currentClaimSearchPage = 1; + } + + contentClaimSearchLoading = true; + Helper.setViewVisibility(noContentView, View.GONE); + Map claimSearchOptions = buildContentOptions(); + contentClaimSearchTask = new ClaimSearchTask(claimSearchOptions, Lbry.LBRY_TV_CONNECTION_STRING, getLoadingView(), new ClaimSearchResultHandler() { + @Override + public void onSuccess(List claims, boolean hasReachedEnd) { + if (contentListAdapter == null) { + contentListAdapter = new ClaimListAdapter(claims, getContext()); + contentListAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() { + @Override + public void onClaimClicked(Claim claim) { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (claim.getName().startsWith("@")) { + // channel claim + activity.openChannelClaim(claim); + } else { + activity.openFileClaim(claim); + } + } + } + }); + } else { + contentListAdapter.addItems(claims); + } + + if (contentList != null && contentList.getAdapter() == null) { + contentList.setAdapter(contentListAdapter); + } + + contentHasReachedEnd = hasReachedEnd; + contentClaimSearchLoading = false; + checkNoContent(); + } + + @Override + public void onError(Exception error) { + contentClaimSearchLoading = false; + checkNoContent(); + } + }); + contentClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void checkNoContent() { + boolean noContent = contentListAdapter == null || contentListAdapter.getItemCount() == 0; + Helper.setViewVisibility(noContentView, noContent ? View.VISIBLE : View.GONE); + } + + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (key.equalsIgnoreCase(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT)) { + fetchClaimSearchContent(true); + } + } + + public void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) { + if ("abort".equals(downloadAction)) { + if (contentListAdapter != null) { + contentListAdapter.clearFileForClaimOrUrl(outpoint, uri); + } + return; + } + + try { + JSONObject fileInfo = new JSONObject(fileInfoJson); + LbryFile claimFile = LbryFile.fromJSONObject(fileInfo); + String claimId = claimFile.getClaimId(); + if (contentListAdapter != null) { + contentListAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri); + } + } catch (JSONException ex) { + // invalid file info for download + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/findcontent/EditorsChoiceFragment.java b/app/src/main/java/io/lbry/browser/ui/findcontent/EditorsChoiceFragment.java new file mode 100644 index 00000000..cd8dd19e --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/findcontent/EditorsChoiceFragment.java @@ -0,0 +1,162 @@ +package io.lbry.browser.ui.findcontent; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; + +import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.EditorsChoiceItemAdapter; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.EditorsChoiceItem; +import io.lbry.browser.tasks.claim.ClaimSearchResultHandler; +import io.lbry.browser.tasks.claim.ClaimSearchTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; + +public class EditorsChoiceFragment extends BaseFragment { + + private static final HashMap titleChannelIdsMap = new LinkedHashMap<>(); + static { + titleChannelIdsMap.put("Short Films", "7056f8267188fc49cd3f7162b4115d9e3c8216f6"); + titleChannelIdsMap.put("Feature-Length Films", "7aad6f36f61da95cb02471fae55f736b28e3bca7"); + titleChannelIdsMap.put("Documentaries", "d57c606e11462e821d5596430c336b58716193bb"); + titleChannelIdsMap.put("Episodic Content", "ea5fc1bd3e1335776fe2641a539a47850606d7db"); + } + + private boolean contentLoading; + private ProgressBar loading; + private RecyclerView contentList; + private EditorsChoiceItemAdapter contentListAdapter; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_editors_choice, container, false); + + loading = root.findViewById(R.id.editors_choice_loading); + contentList = root.findViewById(R.id.editors_choice_content_list); + + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + contentList.setLayoutManager(llm); + + return root; + } + + private Map buildContentOptions() { + Context context = getContext(); + boolean canShowMatureContent = false; + if (context != null) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + } + + return Lbry.buildClaimSearchOptions( + Claim.TYPE_REPOST, + null, + null, /*canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS),*/ + new ArrayList<>(titleChannelIdsMap.values()), + null, + Arrays.asList(Claim.ORDER_BY_RELEASE_TIME), + null, + 1, + 99); + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + Helper.setWunderbarValue(null, context); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Editor's Choice", "EditorsChoice"); + } + + if (contentListAdapter == null || contentListAdapter.getItemCount() == 0) { + fetchClaimSearchContent(); + } else { + if (contentList != null) { + contentList.setAdapter(contentListAdapter); + } + } + } + + public void fetchClaimSearchContent() { + if (contentLoading) { + return; + } + + contentLoading = true; + ClaimSearchTask task = new ClaimSearchTask(buildContentOptions(), Lbry.LBRY_TV_CONNECTION_STRING, loading, new ClaimSearchResultHandler() { + @Override + public void onSuccess(List items, boolean hasReachedEnd) { + List data = buildDataFromClaims(items); + if (contentListAdapter == null) { + contentListAdapter = new EditorsChoiceItemAdapter(data, getContext()); + contentListAdapter.setListener(new EditorsChoiceItemAdapter.EditorsChoiceItemListener() { + @Override + public void onEditorsChoiceItemClicked(EditorsChoiceItem item) { + String url = item.getPermanentUrl(); + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).openFileUrl(url); + } + } + }); + } else { + contentListAdapter.addItems(data); + } + + if (contentList != null && contentList.getAdapter() == null) { + contentList.setAdapter(contentListAdapter); + } + contentLoading = false; + } + + @Override + public void onError(Exception error) { + contentLoading = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private List buildDataFromClaims(List claims) { + List data = new ArrayList<>(); + for (String title : titleChannelIdsMap.keySet()) { + EditorsChoiceItem titleItem = new EditorsChoiceItem(); + titleItem.setTitle(title); + titleItem.setHeader(true); + data.add(titleItem); + + String channelClaimId = titleChannelIdsMap.get(title); + for (Claim c : claims) { + if (c.getSigningChannel() != null && channelClaimId.equalsIgnoreCase(c.getSigningChannel().getClaimId())) { + EditorsChoiceItem item = EditorsChoiceItem.fromClaim( + Claim.TYPE_REPOST.equalsIgnoreCase(c.getValueType()) ? c.getRepostedClaim() : c); + data.add(item); + } + } + } + + return data; + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java b/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java new file mode 100644 index 00000000..3b1a7b82 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java @@ -0,0 +1,2144 @@ +package io.lbry.browser.ui.findcontent; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.graphics.Color; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.format.DateUtils; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.webkit.WebResourceRequest; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.core.content.ContextCompat; +import androidx.media.session.MediaButtonReceiver; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.github.chrisbanes.photoview.PhotoView; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.database.ExoDatabaseProvider; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; +import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; +import com.google.android.exoplayer2.ui.PlayerControlView; +import com.google.android.exoplayer2.ui.PlayerView; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; +import com.google.android.exoplayer2.upstream.Loader; +import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; +import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; +import com.google.android.exoplayer2.upstream.cache.SimpleCache; +import com.google.android.exoplayer2.util.Util; +import com.google.android.flexbox.FlexboxLayoutManager; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; + +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.ClaimListAdapter; +import io.lbry.browser.adapter.TagListAdapter; +import io.lbry.browser.dialog.RepostClaimDialogFragment; +import io.lbry.browser.dialog.SendTipDialogFragment; +import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.listener.DownloadActionListener; +import io.lbry.browser.listener.FetchClaimsListener; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.listener.StoragePermissionListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.ClaimCacheKey; +import io.lbry.browser.model.Fee; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.model.NavMenuItem; +import io.lbry.browser.model.Tag; +import io.lbry.browser.model.UrlSuggestion; +import io.lbry.browser.model.lbryinc.Reward; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.tasks.LighthouseSearchTask; +import io.lbry.browser.tasks.ReadTextFileTask; +import io.lbry.browser.tasks.SetSdkSettingTask; +import io.lbry.browser.tasks.claim.AbandonHandler; +import io.lbry.browser.tasks.claim.AbandonStreamTask; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimSearchResultHandler; +import io.lbry.browser.tasks.claim.ResolveTask; +import io.lbry.browser.tasks.file.DeleteFileTask; +import io.lbry.browser.tasks.file.FileListTask; +import io.lbry.browser.tasks.file.GetFileTask; +import io.lbry.browser.tasks.lbryinc.ChannelSubscribeTask; +import io.lbry.browser.tasks.lbryinc.ClaimRewardTask; +import io.lbry.browser.tasks.lbryinc.FetchStatCountTask; +import io.lbry.browser.tasks.lbryinc.LogFileViewTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.ui.controls.SolidIconView; +import io.lbry.browser.ui.publish.PublishFormFragment; +import io.lbry.browser.ui.publish.PublishFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; +import io.lbry.lbrysdk.DownloadManager; +import io.lbry.lbrysdk.LbrynetService; +import io.lbry.lbrysdk.Utils; + +public class FileViewFragment extends BaseFragment implements + MainActivity.BackPressInterceptor, DownloadActionListener, FetchClaimsListener, SdkStatusListener, StoragePermissionListener { + private static final int RELATED_CONTENT_SIZE = 16; + private static final String DEFAULT_PLAYBACK_SPEED = "1x"; + + private PlayerControlView castControlView; + private Player currentPlayer; + private boolean loadingNewClaim; + private boolean startDownloadPending; + private boolean fileGetPending; + private boolean downloadInProgress; + private boolean downloadRequested; + private boolean loadFilePending; + private boolean resolving; + private boolean initialFileLoadDone; + private Claim claim; + private String currentUrl; + private ClaimListAdapter relatedContentAdapter; + private BroadcastReceiver sdkReceiver; + private Player.EventListener fileViewPlayerListener; + + private long elapsedDuration = 0; + private long totalDuration = 0; + private boolean elapsedPlaybackScheduled; + private ScheduledExecutorService elapsedPlaybackScheduler; + private boolean playbackStarted; + private long startTimeMillis; + private GetFileTask getFileTask; + + private View buttonPublishSomething; + private View layoutLoadingState; + private View layoutNothingAtLocation; + private View layoutDisplayArea; + private View layoutResolving; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_file_view, container, false); + + layoutLoadingState = root.findViewById(R.id.file_view_loading_state); + layoutNothingAtLocation = root.findViewById(R.id.container_nothing_at_location); + layoutResolving = root.findViewById(R.id.file_view_loading_container); + layoutDisplayArea = root.findViewById(R.id.file_view_claim_display_area); + buttonPublishSomething = root.findViewById(R.id.nothing_at_location_publish_button); + + initUi(root); + + fileViewPlayerListener = new Player.EventListener() { + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + if (playbackState == Player.STATE_READY) { + elapsedDuration = MainActivity.appPlayer.getCurrentPosition(); + totalDuration = MainActivity.appPlayer.getDuration() < 0 ? 0 : MainActivity.appPlayer.getDuration(); + if (!playbackStarted) { + logPlay(currentUrl, startTimeMillis); + playbackStarted = true; + } + renderTotalDuration(); + scheduleElapsedPlayback(); + hideBuffering(); + + if (loadingNewClaim) { + MainActivity.appPlayer.setPlayWhenReady(true); + loadingNewClaim = false; + } + } else if (playbackState == Player.STATE_BUFFERING) { + showBuffering(); + } else { + hideBuffering(); + } + } + }; + + return root; + } + + public void onStart() { + super.onStart(); + Context context = getContext(); + + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.setBackPressInterceptor(this); + activity.addDownloadActionListener(this); + activity.addFetchClaimsListener(this); + if (!MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context)) { + activity.addStoragePermissionListener(this); + } + } + } + + private void checkParams() { + boolean updateRequired = false; + Context context = getContext(); + Map params = getParams(); + Claim newClaim = null; + String newUrl = null; + if (params != null) { + if (params.containsKey("claim")) { + newClaim = (Claim) params.get("claim"); + if (newClaim != null && !newClaim.equals(this.claim)) { + updateRequired = true; + } + } + if (params.containsKey("url")) { + newUrl = params.get("url").toString(); + if (claim == null || !newUrl.equalsIgnoreCase(currentUrl)) { + updateRequired = true; + } + } + } else if (currentUrl != null) { + updateRequired = true; + } else if (context instanceof MainActivity) { + ((MainActivity) context).onBackPressed(); + } + + if (updateRequired) { + if (context instanceof MainActivity) { + ((MainActivity) context).clearNowPlayingClaim(); + } + if (MainActivity.appPlayer != null) { + MainActivity.appPlayer.setPlayWhenReady(false); + } + + resetViewCount(); + resetFee(); + checkNewClaimAndUrl(newClaim, newUrl); + + if (newClaim != null) { + claim = newClaim; + } + if (!Helper.isNullOrEmpty(newUrl)) { + // check if the claim is already cached + currentUrl = newUrl; + ClaimCacheKey key = new ClaimCacheKey(); + key.setUrl(currentUrl); + onNewClaim(currentUrl); + if (Lbry.claimCache.containsKey(key)) { + claim = Lbry.claimCache.get(key); + } else { + resolveUrl(currentUrl); + } + } else if (claim == null) { + // nothing at this location + renderNothingAtLocation(); + } + } else { + checkAndResetNowPlayingClaim(); + } + + if (!Helper.isNullOrEmpty(currentUrl)) { + Helper.saveUrlHistory(currentUrl, claim != null ? claim.getTitle() : null, UrlSuggestion.TYPE_FILE); + } + + if (claim != null) { + Helper.saveViewHistory(currentUrl, claim); + renderClaim(); + if (claim.getFile() == null) { + loadFile(); + } else { + initialFileLoadDone = true; + } + } + + checkIsFileComplete(); + } + + private void renderNothingAtLocation() { + Helper.setViewVisibility(layoutLoadingState, View.VISIBLE); + Helper.setViewVisibility(layoutNothingAtLocation, View.VISIBLE); + Helper.setViewVisibility(buttonPublishSomething, View.VISIBLE); + Helper.setViewVisibility(layoutResolving, View.GONE); + Helper.setViewVisibility(layoutDisplayArea, View.INVISIBLE); + } + + private void checkNewClaimAndUrl(Claim newClaim, String newUrl) { + boolean shouldResetNowPlaying = false; + if (newClaim != null && + MainActivity.nowPlayingClaim != null && + !MainActivity.nowPlayingClaim.getClaimId().equalsIgnoreCase(newClaim.getClaimId())) { + shouldResetNowPlaying = true; + } + if (!shouldResetNowPlaying && + newUrl != null && + MainActivity.nowPlayingClaim != null && + !newUrl.equalsIgnoreCase(MainActivity.nowPlayingClaim.getShortUrl()) && + !newUrl.equalsIgnoreCase(MainActivity.nowPlayingClaim.getPermanentUrl())) { + shouldResetNowPlaying = true; + } + + if (shouldResetNowPlaying) { + if (MainActivity.appPlayer != null) { + MainActivity.appPlayer.setPlayWhenReady(false); + } + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).clearNowPlayingClaim(); + resetPlayer(); + } + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (savedInstanceState != null) { + currentUrl = savedInstanceState.getString("url"); + } + } + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + if (savedInstanceState != null) { + savedInstanceState.putString("url", currentUrl); + } + } + + private void initWebView(View view) { + WebView webView = view.findViewById(R.id.file_view_webview); + webView.setWebViewClient(new LbryWebViewClient(getContext())); + WebSettings webSettings = webView.getSettings(); + webSettings.setAllowFileAccess(true); + webSettings.setJavaScriptEnabled(true); + } + + private void logUrlEvent(String url) { + Bundle bundle = new Bundle(); + bundle.putString("uri", url); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_OPEN_FILE_PAGE, bundle); + } + + private void checkAndResetNowPlayingClaim() { + if (MainActivity.nowPlayingClaim != null + && claim != null && + !MainActivity.nowPlayingClaim.getClaimId().equalsIgnoreCase(claim.getClaimId())) { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.clearNowPlayingClaim(); + if (claim != null && !claim.isPlayable()) { + activity.stopExoplayer(); + } + } + } + } + + private void onNewClaim(String url) { + loadingNewClaim = true; + initialFileLoadDone = false; + playbackStarted = false; + currentUrl = url; + logUrlEvent(url); + resetViewCount(); + resetFee(); + View root = getView(); + if (root != null) { + ((RecyclerView) root.findViewById(R.id.file_view_related_content_list)).setAdapter(null); + } + if (MainActivity.appPlayer != null) { + MainActivity.appPlayer.setPlayWhenReady(false); + } + resetPlayer(); + } + + public void onSdkReady() { + if (loadFilePending) { + loadFile(); + } + checkOwnClaim(); + } + + private String getStreamingUrl() { + LbryFile lbryFile = claim.getFile(); + if (lbryFile != null) { + if (!Helper.isNullOrEmpty(lbryFile.getDownloadPath()) && lbryFile.isCompleted()) { + File file = new File(lbryFile.getDownloadPath()); + if (file.exists()) { + return Uri.fromFile(file).toString(); + } + } + + if (!Helper.isNullOrEmpty(lbryFile.getStreamingUrl())) { + return lbryFile.getStreamingUrl(); + } + } + + return buildLbryTvStreamingUrl(); + } + + private String buildLbryTvStreamingUrl() { + return String.format("https://cdn.lbryplayer.xyz/content/claims/%s/%s/stream", claim.getName(), claim.getClaimId()); + } + + private void loadFile() { + if (!Lbry.SDK_READY) { + // make use of the lbry.tv streaming URL + loadFilePending = true; + return; + } + + loadFilePending = false; + String claimId = claim.getClaimId(); + FileListTask task = new FileListTask(claimId, null, new FileListTask.FileListResultHandler() { + @Override + public void onSuccess(List files, boolean hasReachedEnd) { + if (files.size() > 0) { + claim.setFile(files.get(0)); + checkIsFileComplete(); + } + initialFileLoadDone = true; + } + + @Override + public void onError(Exception error) { + initialFileLoadDone = true; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void openClaimUrl(String url) { + resetViewCount(); + resetFee(); + currentUrl = url; + + ClaimCacheKey key = new ClaimCacheKey(); + key.setUrl(currentUrl); + Claim oldClaim = claim; + claim = null; + if (Lbry.claimCache.containsKey(key)) { + claim = Lbry.claimCache.get(key); + if (oldClaim != null && oldClaim.getClaimId().equalsIgnoreCase(claim.getClaimId())) { + // same claim + return; + } + } else { + resolveUrl(currentUrl); + } + + resetMedia(); + onNewClaim(currentUrl); + Helper.setWunderbarValue(currentUrl, getContext()); + + if (claim != null) { + Helper.saveViewHistory(url, claim); + renderClaim(); + } + } + + public void resetMedia() { + View root = getView(); + if (root != null) { + PlayerView view = root.findViewById(R.id.file_view_exoplayer_view); + view.setShutterBackgroundColor(Color.BLACK); + root.findViewById(R.id.file_view_exoplayer_container).setVisibility(View.GONE); + } + if (MainActivity.appPlayer != null) { + MainActivity.appPlayer.stop(); + } + resetPlayer(); + } + + public void onResume() { + super.onResume(); + checkParams(); + + Context context = getContext(); + Helper.setWunderbarValue(currentUrl, context); + if (context instanceof MainActivity) { + LbryAnalytics.setCurrentScreen((MainActivity) context, "File", "File"); + } + + if (MainActivity.appPlayer != null) { + if (MainActivity.playerReassigned) { + setPlayerForPlayerView(); + MainActivity.playerReassigned = false; + } + loadAndScheduleDurations(); + } + + if (Lbry.SDK_READY) { + if (context instanceof MainActivity) { + ((MainActivity) context).addSdkStatusListener(this); + } + } else { + onSdkReady(); + } + } + + public void onStop() { + super.onStop(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.removeDownloadActionListener(this); + activity.removeFetchClaimsListener(this); + activity.removeSdkStatusListener(this); + activity.removeStoragePermissionListener(this); + } + } + + private void setPlayerForPlayerView() { + View root = getView(); + if (root != null) { + PlayerView view = root.findViewById(R.id.file_view_exoplayer_view); + view.setPlayer(null); + view.setPlayer(MainActivity.appPlayer); + } + } + + private void resolveUrl(String url) { + resolving = true; + Helper.setViewVisibility(layoutDisplayArea, View.INVISIBLE); + Helper.setViewVisibility(layoutLoadingState, View.VISIBLE); + Helper.setViewVisibility(layoutNothingAtLocation, View.GONE); + ResolveTask task = new ResolveTask(url, Lbry.LBRY_TV_CONNECTION_STRING, layoutResolving, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + if (claims.size() > 0 && !Helper.isNullOrEmpty(claims.get(0).getClaimId())) { + claim = claims.get(0); + if (Claim.TYPE_REPOST.equalsIgnoreCase(claim.getValueType())) { + claim = claim.getRepostedClaim(); + // cache the reposted claim too for subsequent loads + Lbry.addClaimToCache(claim); + if (claim.getName().startsWith("@")) { + // this is a reposted channel, so finish this activity and launch the channel url + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).openChannelUrl(!Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl()); + } + return; + } + } else { + Lbry.addClaimToCache(claim); + } + + Helper.saveUrlHistory(url, claim.getTitle(), UrlSuggestion.TYPE_FILE); + + // also save view history + Helper.saveViewHistory(url, claim); + + checkAndResetNowPlayingClaim(); + loadFile(); + renderClaim(); + } else { + // render nothing at location + renderNothingAtLocation(); + } + } + + @Override + public void onError(Exception error) { + resolving = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void initUi(View root) { + initWebView(root); + + buttonPublishSomething.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Context context = getContext(); + if (!Helper.isNullOrEmpty(currentUrl) && context instanceof MainActivity) { + LbryUri uri = LbryUri.tryParse(currentUrl); + if (uri != null) { + Map params = new HashMap<>(); + params.put("suggestedUrl", uri.getStreamName()); + ((MainActivity) context).openFragment(PublishFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH, params); + } + } + } + }); + + root.findViewById(R.id.file_view_title_area).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ImageView descIndicator = root.findViewById(R.id.file_view_desc_toggle_arrow); + View descriptionArea = root.findViewById(R.id.file_view_description_area); + + boolean hasDescription = claim != null && !Helper.isNullOrEmpty(claim.getDescription()); + boolean hasTags = claim != null && claim.getTags() != null && claim.getTags().size() > 0; + + if (descriptionArea.getVisibility() != View.VISIBLE) { + if (hasDescription || hasTags) { + descriptionArea.setVisibility(View.VISIBLE); + } + descIndicator.setImageResource(R.drawable.ic_arrow_dropup); + } else { + descriptionArea.setVisibility(View.GONE); + descIndicator.setImageResource(R.drawable.ic_arrow_dropdown); + } + } + }); + + root.findViewById(R.id.file_view_action_share).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null) { + try { + String shareUrl = LbryUri.parse( + !Helper.isNullOrEmpty(claim.getCanonicalUrl()) ? claim.getCanonicalUrl() : + (!Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl())).toTvString(); + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.setType("text/plain"); + shareIntent.putExtra(Intent.EXTRA_TEXT, shareUrl); + + MainActivity.startingShareActivity = true; + Intent shareUrlIntent = Intent.createChooser(shareIntent, getString(R.string.share_lbry_content)); + shareUrlIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(shareUrlIntent); + } catch (LbryUriException ex) { + // pass + } + } + } + }); + + root.findViewById(R.id.file_view_action_tip).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(root.findViewById(R.id.file_view_claim_display_area), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + if (claim != null) { + SendTipDialogFragment dialog = SendTipDialogFragment.newInstance(); + dialog.setClaim(claim); + dialog.setListener(new SendTipDialogFragment.SendTipListener() { + @Override + public void onTipSent(BigDecimal amount) { + double sentAmount = amount.doubleValue(); + String message = getResources().getQuantityString( + R.plurals.you_sent_a_tip, sentAmount == 1.0 ? 1 : 2, + new DecimalFormat("#,###.##").format(sentAmount)); + Snackbar.make(root.findViewById(R.id.file_view_claim_display_area), message, Snackbar.LENGTH_LONG).show(); + } + }); + Context context = getContext(); + if (context instanceof MainActivity) { + dialog.show(((MainActivity) context).getSupportFragmentManager(), SendTipDialogFragment.TAG); + } + } + } + }); + + root.findViewById(R.id.file_view_action_repost).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(root.findViewById(R.id.file_view_claim_display_area), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + if (claim != null) { + RepostClaimDialogFragment dialog = RepostClaimDialogFragment.newInstance(); + dialog.setClaim(claim); + dialog.setListener(new RepostClaimDialogFragment.RepostClaimListener() { + @Override + public void onClaimReposted(Claim claim) { + Snackbar.make(root.findViewById(R.id.file_view_claim_display_area), R.string.content_successfully_reposted, Snackbar.LENGTH_LONG).show(); + } + }); + Context context = getContext(); + if (context instanceof MainActivity) { + dialog.show(((MainActivity) context).getSupportFragmentManager(), RepostClaimDialogFragment.TAG); + } + } + } + }); + + root.findViewById(R.id.file_view_action_edit).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(root.findViewById(R.id.file_view_claim_display_area), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + Context context = getContext(); + if (claim != null && context instanceof MainActivity) { + ((MainActivity) context).openPublishForm(claim); + } + } + }); + + root.findViewById(R.id.file_view_action_delete).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(root.findViewById(R.id.file_view_claim_display_area), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + if (claim != null) { + boolean isOwnClaim = Lbry.ownClaims.contains(claim); + if (isOwnClaim) { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()). + setTitle(R.string.delete_content). + setMessage(R.string.confirm_delete_content_message) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + deleteCurrentClaim(); + } + }).setNegativeButton(R.string.no, null); + builder.show(); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()). + setTitle(R.string.delete_file). + setMessage(R.string.confirm_delete_file_message) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + deleteClaimFile(); + } + }).setNegativeButton(R.string.no, null); + builder.show(); + } + } + } + }); + + root.findViewById(R.id.file_view_action_download).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(root.findViewById(R.id.file_view_claim_display_area), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + if (claim != null) { + if (downloadInProgress) { + onDownloadAborted(); + + // file is already downloading and not completed + Intent intent = new Intent(LbrynetService.ACTION_DELETE_DOWNLOAD); + intent.putExtra("uri", claim.getPermanentUrl()); + intent.putExtra("nativeDelete", true); + Context context = getContext(); + if (context != null) { + context.sendBroadcast(intent); + } + } else { + checkStoragePermissionAndStartDownload(); + } + } + } + }); + + root.findViewById(R.id.file_view_action_report).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("https://lbry.com/dmca/%s", claim.getClaimId()))); + startActivity(intent); + } + } + }); + + root.findViewById(R.id.player_toggle_cast).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + toggleCast(); + } + }); + + PlayerView playerView = root.findViewById(R.id.file_view_exoplayer_view); + View playbackSpeedContainer = playerView.findViewById(R.id.player_playback_speed); + TextView textPlaybackSpeed = playerView.findViewById(R.id.player_playback_speed_label); + textPlaybackSpeed.setText(DEFAULT_PLAYBACK_SPEED); + + playbackSpeedContainer.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { + @Override + public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) { + Helper.buildPlaybackSpeedMenu(contextMenu); + } + }); + playbackSpeedContainer.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).openContextMenu(playbackSpeedContainer); + } + } + }); + + playerView.findViewById(R.id.player_toggle_fullscreen).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // check full screen mode + if (isInFullscreenMode()) { + disableFullScreenMode(); + } else { + enableFullScreenMode(); + } + } + }); + playerView.findViewById(R.id.player_skip_back_10).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (MainActivity.appPlayer != null) { + MainActivity.appPlayer.seekTo(Math.max(0, MainActivity.appPlayer.getCurrentPosition() - 10000)); + } + } + }); + playerView.findViewById(R.id.player_skip_forward_10).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (MainActivity.appPlayer != null) { + MainActivity.appPlayer.seekTo(MainActivity.appPlayer.getCurrentPosition() + 10000); + } + } + }); + + root.findViewById(R.id.file_view_publisher_name).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null && claim.getSigningChannel() != null) { + Claim publisher = claim.getSigningChannel(); + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).openChannelUrl( + !Helper.isNullOrEmpty(publisher.getShortUrl()) ? publisher.getShortUrl() : publisher.getPermanentUrl()); + } + } + } + }); + + View buttonFollowUnfollow = root.findViewById(R.id.file_view_icon_follow_unfollow); + buttonFollowUnfollow.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null && claim.getSigningChannel() != null) { + Claim publisher = claim.getSigningChannel(); + boolean isFollowing = Lbryio.isFollowing(publisher); + Subscription subscription = Subscription.fromClaim(publisher); + buttonFollowUnfollow.setEnabled(false); + Context context = getContext(); + new ChannelSubscribeTask(context, publisher.getClaimId(), subscription, isFollowing, new ChannelSubscribeTask.ChannelSubscribeHandler() { + @Override + public void onSuccess() { + if (isFollowing) { + Lbryio.removeSubscription(subscription); + Lbryio.removeCachedResolvedSubscription(publisher); + } else { + Lbryio.addSubscription(subscription); + Lbryio.addCachedResolvedSubscription(publisher); + } + buttonFollowUnfollow.setEnabled(true); + checkIsFollowing(); + FollowingFragment.resetClaimSearchContent = true; + + // Save shared user state + if (context != null) { + context.sendBroadcast(new Intent(MainActivity.ACTION_SAVE_SHARED_USER_STATE)); + } + } + + @Override + public void onError(Exception exception) { + buttonFollowUnfollow.setEnabled(true); + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + }); + + RecyclerView relatedContentList = root.findViewById(R.id.file_view_related_content_list); + relatedContentList.setNestedScrollingEnabled(false); + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + relatedContentList.setLayoutManager(llm); + } + + private void deleteCurrentClaim() { + if (claim != null) { + Helper.setViewVisibility(layoutDisplayArea, View.INVISIBLE); + Helper.setViewVisibility(layoutLoadingState, View.VISIBLE); + Helper.setViewVisibility(layoutNothingAtLocation, View.GONE); + AbandonStreamTask task = new AbandonStreamTask(Arrays.asList(claim.getClaimId()), layoutResolving, new AbandonHandler() { + @Override + public void onComplete(List successfulClaimIds, List failedClaimIds, List errors) { + Context context = getContext(); + if (context instanceof MainActivity) { + if (failedClaimIds.size() == 0) { + MainActivity activity = (MainActivity) context; + activity.showMessage(R.string.content_deleted); + activity.onBackPressed(); + } else { + showError(getString(R.string.content_failed_delete)); + } + } + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + private void checkStoragePermissionAndStartDownload() { + Context context = getContext(); + if (MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context)) { + startDownload(); + } else { + startDownloadPending = true; + MainActivity.requestPermission( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + MainActivity.REQUEST_STORAGE_PERMISSION, + getString(R.string.storage_permission_rationale_download), + context, + true); + } + } + + private void checkStoragePermissionAndFileGet() { + Context context = getContext(); + if (!MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context)) { + fileGetPending = true; + MainActivity.requestPermission( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + MainActivity.REQUEST_STORAGE_PERMISSION, + getString(R.string.storage_permission_rationale_download), + context, + true); + } else { + fileGet(true); + } + } + + public void onStoragePermissionGranted() { + Context context = getContext(); + SetSdkSettingTask task = null; + if (startDownloadPending) { + startDownloadPending = false; + task = new SetSdkSettingTask("download_dir", Utils.getConfiguredDownloadDirectory(context), new GenericTaskHandler() { + @Override + public void beforeStart() { } + @Override + public void onSuccess() { startDownload(); } + @Override + public void onError(Exception error) { + // start the download anyway. Only that it will be saved in the app private folder: /sdcard/Android/io.lbry.browser/Download + startDownload(); + } + }); + } else if (fileGetPending) { + fileGetPending = false; + task = new SetSdkSettingTask("download_dir", Utils.getConfiguredDownloadDirectory(context), new GenericTaskHandler() { + @Override + public void beforeStart() { } + @Override + public void onSuccess() { fileGet(true); } + @Override + public void onError(Exception error) { + // start the file get anyway. Only that it will be saved in the app private folder: /sdcard/Android/io.lbry.browser/Download + fileGet(true); + } + }); + } + if (task != null) { + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + public void onStoragePermissionRefused() { + fileGetPending = false; + startDownloadPending = false; + onDownloadAborted(); + Snackbar.make(getView(), R.string.storage_permission_rationale_download, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } + + public void startDownload() { + downloadInProgress = true; + + View root = getView(); + if (root != null) { + Helper.setViewVisibility(root.findViewById(R.id.file_view_download_progress), View.VISIBLE); + ((ImageView) root.findViewById(R.id.file_view_action_download_icon)).setImageResource(R.drawable.ic_stop); + } + + if (!claim.isFree()) { + downloadRequested = true; + onMainActionButtonClicked(); + } else { + // download the file + fileGet(true); + } + } + + private void deleteClaimFile() { + if (claim != null) { + View actionDelete = getView().findViewById(R.id.file_view_action_delete); + DeleteFileTask task = new DeleteFileTask(claim.getClaimId(), new GenericTaskHandler() { + @Override + public void beforeStart() { + actionDelete.setEnabled(false); + } + + @Override + public void onSuccess() { + Helper.setViewVisibility(actionDelete, View.GONE); + View root = getView(); + if (root != null) { + root.findViewById(R.id.file_view_action_download).setVisibility(View.VISIBLE); + root.findViewById(R.id.file_view_unsupported_container).setVisibility(View.GONE); + } + Helper.setViewEnabled(actionDelete, true); + + claim.setFile(null); + Lbry.unsetFilesForCachedClaims(Arrays.asList(claim.getClaimId())); + + restoreMainActionButton(); + } + + @Override + public void onError(Exception error) { + actionDelete.setEnabled(true); + showError(error.getMessage()); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + private void renderClaim() { + if (claim == null) { + return; + } + + if (claim.isPlayable() && MainActivity.appPlayer != null) { + MainActivity.appPlayer.setPlayWhenReady(true); + } + + Helper.setViewVisibility(layoutLoadingState, View.GONE); + Helper.setViewVisibility(layoutNothingAtLocation, View.GONE); + + loadViewCount(); + checkIsFollowing(); + + View root = getView(); + if (root != null) { + root.findViewById(R.id.file_view_scroll_view).scrollTo(0, 0); + Helper.setViewVisibility(layoutDisplayArea, View.VISIBLE); + + ImageView descIndicator = root.findViewById(R.id.file_view_desc_toggle_arrow); + descIndicator.setImageResource(R.drawable.ic_arrow_dropdown); + + boolean hasDescription = !Helper.isNullOrEmpty(claim.getDescription()); + boolean hasTags = claim.getTags() != null && claim.getTags().size() > 0; + + root.findViewById(R.id.file_view_description).setVisibility(hasDescription ? View.VISIBLE : View.GONE); + root.findViewById(R.id.file_view_tag_area).setVisibility(hasTags ? View.VISIBLE : View.GONE); + if (hasTags && !hasDescription) { + root.findViewById(R.id.file_view_tag_area).setPadding(0, 0, 0, 0); + } + + root.findViewById(R.id.file_view_description_area).setVisibility(View.GONE); + ((TextView) root.findViewById(R.id.file_view_title)).setText(claim.getTitle()); + ((TextView) root.findViewById(R.id.file_view_description)).setText(claim.getDescription()); + ((TextView) root.findViewById(R.id.file_view_publisher_name)).setText( + Helper.isNullOrEmpty(claim.getPublisherName()) ? getString(R.string.anonymous) : claim.getPublisherName()); + + Context context = getContext(); + RecyclerView descTagsList = root.findViewById(R.id.file_view_tag_list); + FlexboxLayoutManager flm = new FlexboxLayoutManager(context); + descTagsList.setLayoutManager(flm); + + List tags = claim.getTagObjects(); + TagListAdapter tagListAdapter = new TagListAdapter(tags, context); + tagListAdapter.setClickListener(new TagListAdapter.TagClickListener() { + @Override + public void onTagClicked(Tag tag, int customizeMode) { + if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_NONE) { + Context ctx = getContext(); + if (ctx instanceof MainActivity) { + ((MainActivity) ctx).openAllContentFragmentWithTag(tag.getName()); + } + } + } + }); + descTagsList.setAdapter(tagListAdapter); + root.findViewById(R.id.file_view_tag_area).setVisibility(tags.size() > 0 ? View.VISIBLE : View.GONE); + + root.findViewById(R.id.file_view_exoplayer_container).setVisibility(View.GONE); + root.findViewById(R.id.file_view_unsupported_container).setVisibility(View.GONE); + root.findViewById(R.id.file_view_media_meta_container).setVisibility(View.VISIBLE); + + Claim.GenericMetadata metadata = claim.getValue(); + if (!Helper.isNullOrEmpty(claim.getThumbnailUrl())) { + ImageView thumbnailView = root.findViewById(R.id.file_view_thumbnail); + Glide.with(getContext().getApplicationContext()).load(claim.getThumbnailUrl()).centerCrop().into(thumbnailView); + } else { + // display first x letters of claim name, with random background + } + + root.findViewById(R.id.file_view_main_action_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onMainActionButtonClicked(); + } + }); + root.findViewById(R.id.file_view_media_meta_container).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onMainActionButtonClicked(); + } + }); + + if (metadata instanceof Claim.StreamMetadata) { + Claim.StreamMetadata streamMetadata = (Claim.StreamMetadata) metadata; + long publishTime = streamMetadata.getReleaseTime() > 0 ? streamMetadata.getReleaseTime() * 1000 : claim.getTimestamp() * 1000; + ((TextView) root.findViewById(R.id.file_view_publish_time)).setText(DateUtils.getRelativeTimeSpanString( + publishTime, System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE)); + + Fee fee = streamMetadata.getFee(); + if (fee != null && Helper.parseDouble(fee.getAmount(), 0) > 0) { + root.findViewById(R.id.file_view_fee_container).setVisibility(View.VISIBLE); + ((TextView) root.findViewById(R.id.file_view_fee)).setText( + Helper.shortCurrencyFormat(claim.getActualCost(Lbryio.LBCUSDRate).doubleValue())); + } + } + + root.findViewById(R.id.file_view_icon_follow_unfollow).setVisibility(claim.getSigningChannel() != null ? View.VISIBLE : View.GONE); + + MaterialButton mainActionButton = root.findViewById(R.id.file_view_main_action_button); + if (claim.isPlayable()) { + mainActionButton.setText(R.string.play); + } else if (claim.isViewable()) { + mainActionButton.setText(R.string.view); + } else { + mainActionButton.setText(R.string.download); + } + + if (claim.isFree()) { + if (claim.isPlayable()) { + if (MainActivity.nowPlayingClaim != null && MainActivity.nowPlayingClaim.getClaimId().equalsIgnoreCase(claim.getClaimId())) { + // claim already playing + showExoplayerView(); + playMedia(); + } else { + onMainActionButtonClicked(); + } + } else if (claim.isViewable() && Lbry.SDK_READY) { + onMainActionButtonClicked(); + } else if (!Lbry.SDK_READY) { + restoreMainActionButton(); + } + } else { + restoreMainActionButton(); + } + + RecyclerView relatedContentList = root.findViewById(R.id.file_view_related_content_list); + if (relatedContentList == null || relatedContentList.getAdapter() == null || relatedContentList.getAdapter().getItemCount() == 0) { + loadRelatedContent(); + } + } + checkOwnClaim(); + } + + private void showUnsupportedView() { + getView().findViewById(R.id.file_view_exoplayer_container).setVisibility(View.GONE); + getView().findViewById(R.id.file_view_unsupported_container).setVisibility(View.VISIBLE); + String fileNameString = ""; + if (claim.getFile() != null) { + LbryFile lbryFile = claim.getFile(); + File file = new File(lbryFile.getDownloadPath()); + fileNameString = String.format("\"%s\" ", file.getName()); + } + ((TextView) getView().findViewById(R.id.file_view_unsupported_text)).setText(getString(R.string.unsupported_content_desc, fileNameString)); + } + + private void showExoplayerView() { + View root = getView(); + if (root != null) { + root.findViewById(R.id.file_view_unsupported_container).setVisibility(View.GONE); + root.findViewById(R.id.file_view_exoplayer_container).setVisibility(View.VISIBLE); + } + } + + private void playMedia() { + boolean newPlayerCreated = false; + + Context context = getContext(); + if (MainActivity.appPlayer == null && context != null) { + AudioAttributes audioAttributes = new AudioAttributes.Builder() + .setUsage(C.USAGE_MEDIA) + .setContentType(C.CONTENT_TYPE_MOVIE) + .build(); + + MainActivity.appPlayer = new SimpleExoPlayer.Builder(context).build(); + MainActivity.appPlayer.setAudioAttributes(audioAttributes, true); + MainActivity.playerCache = + new SimpleCache(context.getCacheDir(), + new LeastRecentlyUsedCacheEvictor(1024 * 1024 * 256), new ExoDatabaseProvider(context)); + if (context instanceof MainActivity) { + ((MainActivity) context).initMediaSession(); + } + + newPlayerCreated = true; + } + + View root = getView(); + if (root != null) { + PlayerView view = root.findViewById(R.id.file_view_exoplayer_view); + view.setShutterBackgroundColor(Color.TRANSPARENT); + view.setPlayer(MainActivity.appPlayer); + view.setUseController(true); + if (context instanceof MainActivity) { + ((MainActivity) context).getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + if (MainActivity.nowPlayingClaim != null && + MainActivity.nowPlayingClaim.getClaimId().equalsIgnoreCase(claim.getClaimId()) && + !newPlayerCreated) { + // if the claim is already playing, we don't need to reload the media source + return; + } + + if (MainActivity.appPlayer != null) { + showBuffering(); + if (fileViewPlayerListener != null) { + MainActivity.appPlayer.addListener(fileViewPlayerListener); + } + if (context instanceof MainActivity) { + ((MainActivity) context).setNowPlayingClaim(claim, currentUrl); + } + + MainActivity.appPlayer.setPlayWhenReady(true); + String userAgent = Util.getUserAgent(context, getString(R.string.app_name)); + String mediaSourceUrl = getStreamingUrl(); + MediaSource mediaSource = new ProgressiveMediaSource.Factory( + new CacheDataSourceFactory(MainActivity.playerCache, new DefaultDataSourceFactory(context, userAgent)), + new DefaultExtractorsFactory() + ).setLoadErrorHandlingPolicy(new StreamLoadErrorPolicy()).createMediaSource(Uri.parse(mediaSourceUrl)); + + MainActivity.appPlayer.prepare(mediaSource, true, true); + } + } + } + + private void setCurrentPlayer(Player currentPlayer) { + if (this.currentPlayer == currentPlayer) { + return; + } + + // View management. + if (currentPlayer == MainActivity.appPlayer) { + //localPlayerView.setVisibility(View.VISIBLE); + castControlView.hide(); + ((ImageView) getView().findViewById(R.id.player_image_cast_toggle)).setImageResource(R.drawable.ic_cast); + } else /* currentPlayer == castPlayer */ { + castControlView.show(); + ((ImageView) getView().findViewById(R.id.player_image_cast_toggle)).setImageResource(R.drawable.ic_cast_connected); + } + + // Player state management. + long playbackPositionMs = C.TIME_UNSET; + int windowIndex = C.INDEX_UNSET; + boolean playWhenReady = false; + + Player previousPlayer = this.currentPlayer; + if (previousPlayer != null) { + // Save state from the previous player. + int playbackState = previousPlayer.getPlaybackState(); + if (playbackState != Player.STATE_ENDED) { + playbackPositionMs = previousPlayer.getCurrentPosition(); + playWhenReady = previousPlayer.getPlayWhenReady(); + } + previousPlayer.stop(true); + } + + this.currentPlayer = currentPlayer; + + // Media queue management. + /*if (currentPlayer == exoPlayer) { + exoPlayer.prepare(concatenatingMediaSource); + }*/ + currentPlayer.seekTo(playbackPositionMs); + currentPlayer.setPlayWhenReady(true); + } + + private void resetViewCount() { + View root = getView(); + if (root != null) { + TextView textViewCount = root.findViewById(R.id.file_view_view_count); + Helper.setViewText(textViewCount, null); + Helper.setViewVisibility(textViewCount, View.GONE); + } + } + private void resetFee() { + View root = getView(); + if (root != null) { + TextView feeView = root.findViewById(R.id.file_view_fee); + feeView.setText(null); + Helper.setViewVisibility(root.findViewById(R.id.file_view_fee_container), View.GONE); + } + } + + private void loadViewCount() { + if (claim != null) { + FetchStatCountTask task = new FetchStatCountTask( + FetchStatCountTask.STAT_VIEW_COUNT, claim.getClaimId(), null, new FetchStatCountTask.FetchStatCountHandler() { + @Override + public void onSuccess(int count) { + try { + String displayText = getResources().getQuantityString(R.plurals.view_count, count, NumberFormat.getInstance().format(count)); + View root = getView(); + if (root != null) { + TextView textViewCount = root.findViewById(R.id.file_view_view_count); + Helper.setViewText(textViewCount, displayText); + Helper.setViewVisibility(textViewCount, View.VISIBLE); + } + } catch (IllegalStateException ex) { + // pass + } + } + + @Override + public void onError(Exception error) { + // pass + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + private void onMainActionButtonClicked() { + // Check if the claim is free + Claim.GenericMetadata metadata = claim.getValue(); + if (metadata instanceof Claim.StreamMetadata) { + Claim.StreamMetadata streamMetadata = (Claim.StreamMetadata) metadata; + if (claim.getFile() == null && !claim.isFree()) { + // not free (and the user does not own the claim yet), perform a purchase + confirmPurchaseUrl(); + } else { + if (!claim.isPlayable() && !Lbry.SDK_READY) { + Snackbar.make(getView().findViewById(R.id.file_view_global_layout), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + getView().findViewById(R.id.file_view_main_action_button).setVisibility(View.INVISIBLE); + getView().findViewById(R.id.file_view_main_action_loading).setVisibility(View.VISIBLE); + handleMainActionForClaim(); + } + } else { + showError(getString(R.string.cannot_view_claim)); + } + } + + private void confirmPurchaseUrl() { + if (claim != null) { + Fee fee = ((Claim.StreamMetadata) claim.getValue()).getFee(); + double cost = claim.getActualCost(Lbryio.LBCUSDRate).doubleValue(); + String formattedCost = Helper.LBC_CURRENCY_FORMAT.format(cost); + String message = getResources().getQuantityString( + R.plurals.confirm_purchase_message, + cost == 1 ? 1 : 2, + claim.getTitle(), + formattedCost.equals("0") ? Helper.FULL_LBC_CURRENCY_FORMAT.format(cost) : formattedCost); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()). + setTitle(R.string.confirm_purchase). + setMessage(message) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + Bundle bundle = new Bundle(); + bundle.putString("uri", currentUrl); + bundle.putString("paid", "true"); + bundle.putDouble("amount", Helper.parseDouble(fee.getAmount(), 0)); + bundle.putDouble("lbc_amount", cost); + bundle.putString("currency", fee.getCurrency()); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_PURCHASE_URI, bundle); + + getView().findViewById(R.id.file_view_main_action_button).setVisibility(View.INVISIBLE); + getView().findViewById(R.id.file_view_main_action_loading).setVisibility(View.VISIBLE); + handleMainActionForClaim(); + } + }).setNegativeButton(R.string.no, null); + builder.show(); + } + } + + private void tryOpenFileOrFileGet() { + if (claim != null) { + String claimId = claim.getClaimId(); + FileListTask task = new FileListTask(claimId, null, new FileListTask.FileListResultHandler() { + @Override + public void onSuccess(List files, boolean hasReachedEnd) { + if (files.size() > 0) { + claim.setFile(files.get(0)); + handleMainActionForClaim(); + checkIsFileComplete(); + } else { + checkStoragePermissionAndFileGet(); + } + } + + @Override + public void onError(Exception error) { + checkStoragePermissionAndFileGet(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + private void handleMainActionForClaim() { + if (Lbry.SDK_READY) { + // Check if the file already exists for the claim + if (claim.getFile() != null) { + playOrViewMedia(); + } else { + // check if the file exists from file list + boolean saveFile = downloadRequested || !claim.isPlayable(); + if (!saveFile) { + startTimeMillis = System.currentTimeMillis(); + fileGet(false); + return; + } else { + tryOpenFileOrFileGet(); + } + } + } else { + if (claim.isPlayable()) { + startTimeMillis = System.currentTimeMillis(); + showExoplayerView(); + playMedia(); + } else { + Snackbar.make(getView().findViewById(R.id.file_view_global_layout), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + } + } + } + + private void fileGet(boolean save) { + if (getFileTask != null && getFileTask.getStatus() != AsyncTask.Status.FINISHED) { + return; + } + getFileTask = new GetFileTask(claim.getPermanentUrl(), save, null, new GetFileTask.GetFileHandler() { + @Override + public void beforeStart() { + + } + + @Override + public void onSuccess(LbryFile file, boolean saveFile) { + // queue the download + if (claim != null) { + if (claim.isFree()) { + // paid is handled differently + Bundle bundle = new Bundle(); + bundle.putString("uri", currentUrl); + bundle.putString("paid", "false"); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_PURCHASE_URI, bundle); + } + + if (!claim.isPlayable()) { + logFileView(claim.getPermanentUrl(), 0); + } + + claim.setFile(file); + if (saveFile) { + // download + String outpoint = String.format("%s:%d", claim.getTxid(), claim.getNout()); + Intent intent = new Intent(LbrynetService.ACTION_QUEUE_DOWNLOAD); + intent.putExtra("outpoint", outpoint); + Context context = getContext(); + if (context != null) { + context.sendBroadcast(intent); + } + } else { + // streaming + playOrViewMedia(); + } + } + } + + @Override + public void onError(Exception error, boolean saveFile) { + try { + showError(getString(R.string.unable_to_view_url, currentUrl)); + if (saveFile) { + onDownloadAborted(); + } + restoreMainActionButton(); + } catch (IllegalStateException ex) { + // pass + } + } + }); + getFileTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void playOrViewMedia() { + boolean handled = false; + String mediaType = claim.getMediaType(); + if (!Helper.isNullOrEmpty(mediaType)) { + if (claim.isPlayable()) { + startTimeMillis = System.currentTimeMillis(); + showExoplayerView(); + playMedia(); + handled = true; + } else if (claim.isViewable()) { + // check type and display + boolean fileExists = false; + LbryFile claimFile = claim.getFile(); + Uri fileUri = null; + if (claimFile != null && !Helper.isNullOrEmpty(claimFile.getDownloadPath())) { + File file = new File(claimFile.getDownloadPath()); + fileUri = Uri.fromFile(file); + fileExists = file.exists(); + } + if (!fileExists) { + showError(getString(R.string.claim_file_not_found, claimFile != null ? claimFile.getDownloadPath() : "")); + } else if (fileUri != null) { + if (mediaType.startsWith("image")) { + // display the image + View container = getView().findViewById(R.id.file_view_imageviewer_container); + PhotoView photoView = getView().findViewById(R.id.file_view_imageviewer); + + Glide.with(getContext().getApplicationContext()).load(fileUri).centerInside().into(photoView); + hideFloatingWalletBalance(); + container.setVisibility(View.VISIBLE); + } else if (mediaType.startsWith("text")) { + // show web view (and parse markdown too) + View container = getView().findViewById(R.id.file_view_webview_container); + WebView webView = getView().findViewById(R.id.file_view_webview); + if (Arrays.asList("text/markdown", "text/md").contains(mediaType.toLowerCase())) { + loadMarkdownFromFile(claimFile.getDownloadPath()); + } else { + webView.loadUrl(fileUri.toString()); + } + hideFloatingWalletBalance(); + container.setVisibility(View.VISIBLE); + } + handled = true; + } + } + } + + if (!handled) { + showUnsupportedView(); + } + } + + private void loadMarkdownFromFile(String filePath) { + ReadTextFileTask task = new ReadTextFileTask(filePath, new ReadTextFileTask.ReadTextFileHandler() { + @Override + public void onSuccess(String text) { + String html = buildMarkdownHtml(text); + WebView webView = getView().findViewById(R.id.file_view_webview); + webView.loadData(html, "text/html", "utf-8"); + } + + @Override + public void onError(Exception error) { + showError(error.getMessage()); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private String buildMarkdownHtml(String markdown) { + Parser parser = Parser.builder().build(); + Node document = parser.parse(markdown); + HtmlRenderer renderer = HtmlRenderer.builder().build(); + String markdownHtml = renderer.render(document); + + return "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + markdownHtml + + "
\n" + + " \n" + + " "; + } + + public void showError(String message) { + View root = getView(); + if (root != null) { + Snackbar.make(root, message, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } + } + + private void loadRelatedContent() { + // reset the list view + View root = getView(); + if (claim != null && root != null) { + String title = claim.getTitle(); + String claimId = claim.getClaimId(); + ProgressBar relatedLoading = root.findViewById(R.id.file_view_related_content_progress); + Context context = getContext(); + boolean canShowMatureContent = false; + if (context != null) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + } + LighthouseSearchTask relatedTask = new LighthouseSearchTask( + title, RELATED_CONTENT_SIZE, 0, canShowMatureContent, claimId, relatedLoading, new ClaimSearchResultHandler() { + @Override + public void onSuccess(List claims, boolean hasReachedEnd) { + List filteredClaims = new ArrayList<>(); + for (Claim c : claims) { + if (!c.getClaimId().equalsIgnoreCase(claim.getClaimId())) { + filteredClaims.add(c); + } + } + + relatedContentAdapter = new ClaimListAdapter(filteredClaims, context); + relatedContentAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() { + @Override + public void onClaimClicked(Claim claim) { + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (claim.getName().startsWith("@")) { + activity.openChannelUrl(claim.getPermanentUrl()); + } else { + activity.openFileUrl(claim.getPermanentUrl()); //openClaimUrl(claim.getPermanentUrl()); + } + } + } + }); + + View v = getView(); + if (v != null) { + RecyclerView relatedContentList = root.findViewById(R.id.file_view_related_content_list); + relatedContentList.setAdapter(relatedContentAdapter); + relatedContentAdapter.notifyDataSetChanged(); + + Helper.setViewVisibility( + v.findViewById(R.id.file_view_no_related_content), + relatedContentAdapter == null || relatedContentAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + } + + @Override + public void onError(Exception error) { + + } + }); + relatedTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + public boolean onBackPressed() { + if (isInFullscreenMode()) { + disableFullScreenMode(); + return true; + } + + if (isImageViewerVisible()) { + getView().findViewById(R.id.file_view_imageviewer_container).setVisibility(View.GONE); + restoreMainActionButton(); + showFloatingWalletBalance(); + return true; + } + if (isWebViewVisible()) { + getView().findViewById(R.id.file_view_webview_container).setVisibility(View.GONE); + restoreMainActionButton(); + showFloatingWalletBalance(); + return true; + } + + return false; + } + + private boolean isImageViewerVisible() { + View view = getView(); + return view != null && view.findViewById(R.id.file_view_imageviewer_container).getVisibility() == View.VISIBLE; + } + + private boolean isWebViewVisible() { + View view = getView(); + return view != null && view.findViewById(R.id.file_view_webview_container).getVisibility() == View.VISIBLE; + } + + @SuppressLint("SourceLockedOrientationActivity") + private void enableFullScreenMode() { + Context context = getContext(); + if (context instanceof MainActivity) { + View root = getView(); + ConstraintLayout globalLayout = root.findViewById(R.id.file_view_global_layout); + View exoplayerContainer = root.findViewById(R.id.file_view_exoplayer_container); + ((ViewGroup) exoplayerContainer.getParent()).removeView(exoplayerContainer); + globalLayout.addView(exoplayerContainer); + + View playerView = root.findViewById(R.id.file_view_exoplayer_view); + ((ImageView) playerView.findViewById(R.id.player_image_full_screen_toggle)).setImageResource(R.drawable.ic_fullscreen_exit); + + root.findViewById(R.id.player_image_full_screen_toggle).setVisibility(View.GONE); + + MainActivity activity = (MainActivity) context; + activity.enterFullScreenMode(); + + int statusBarHeight = activity.getStatusBarHeight(); + exoplayerContainer.setPadding(0, 0, 0, statusBarHeight); + + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } + } + + private void disableFullScreenMode() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + View root = getView(); + + RelativeLayout mediaContainer = root.findViewById(R.id.file_view_media_container); + View exoplayerContainer = root.findViewById(R.id.file_view_exoplayer_container); + ((ViewGroup) exoplayerContainer.getParent()).removeView(exoplayerContainer); + mediaContainer.addView(exoplayerContainer); + + View playerView = root.findViewById(R.id.file_view_exoplayer_view); + ((ImageView) playerView.findViewById(R.id.player_image_full_screen_toggle)).setImageResource(R.drawable.ic_fullscreen); + exoplayerContainer.setPadding(0, 0, 0, 0); + + activity.exitFullScreenMode(); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + } + } + + private boolean isInFullscreenMode() { + View view = getView(); + if (view != null) { + View exoplayerContainer = view.findViewById(R.id.file_view_exoplayer_container); + return exoplayerContainer.getParent() instanceof ConstraintLayout; + } + return false; + } + + private void scheduleElapsedPlayback() { + if (!elapsedPlaybackScheduled) { + elapsedPlaybackScheduler = Executors.newSingleThreadScheduledExecutor(); + elapsedPlaybackScheduler.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).runOnUiThread(new Runnable() { + @Override + public void run() { + if (MainActivity.appPlayer != null) { + elapsedDuration = MainActivity.appPlayer.getCurrentPosition(); + renderElapsedDuration(); + } + } + }); + } + } + }, 0, 500, TimeUnit.MILLISECONDS); + elapsedPlaybackScheduled = true; + } + } + + private void resetPlayer() { + elapsedDuration = 0; + totalDuration = 0; + renderElapsedDuration(); + renderTotalDuration(); + + elapsedPlaybackScheduled = false; + if (elapsedPlaybackScheduler != null) { + elapsedPlaybackScheduler.shutdownNow(); + elapsedPlaybackScheduler = null; + } + + playbackStarted = false; + startTimeMillis = 0; + + if (MainActivity.appPlayer != null) { + MainActivity.appPlayer.removeListener(fileViewPlayerListener); + PlaybackParameters params = new PlaybackParameters(1.0f); + MainActivity.appPlayer.setPlaybackParameters(params); + } + } + + private void showBuffering() { + View view = getView(); + if (view != null) { + view.findViewById(R.id.player_buffering_progress).setVisibility(View.VISIBLE); + } + } + + private void hideBuffering() { + View view = getView(); + if (view != null) { + view.findViewById(R.id.player_buffering_progress).setVisibility(View.INVISIBLE); + } + } + + private void renderElapsedDuration() { + View view = getView(); + if (view != null) { + Helper.setViewText(view.findViewById(R.id.player_duration_elapsed), Helper.formatDuration(Double.valueOf(elapsedDuration / 1000.0).longValue())); + } + } + + private void renderTotalDuration() { + View view = getView(); + if (view != null) { + Helper.setViewText(view.findViewById(R.id.player_duration_total), Helper.formatDuration(Double.valueOf(totalDuration / 1000.0).longValue())); + } + } + + private void loadAndScheduleDurations() { + if (MainActivity.appPlayer != null && playbackStarted) { + elapsedDuration = MainActivity.appPlayer.getCurrentPosition() < 0 ? 0 : MainActivity.appPlayer.getCurrentPosition(); + totalDuration = MainActivity.appPlayer.getDuration() < 0 ? 0 : MainActivity.appPlayer.getDuration(); + + renderElapsedDuration(); + renderTotalDuration(); + scheduleElapsedPlayback(); + } + } + + private void logPlay(String url, long startTimeMillis) { + long timeToStartMillis = startTimeMillis > 0 ? System.currentTimeMillis() - startTimeMillis : 0; + + Bundle bundle = new Bundle(); + bundle.putString("uri", url); + bundle.putLong("time_to_start_ms", timeToStartMillis); + bundle.putLong("time_to_start_seconds", Double.valueOf(timeToStartMillis / 1000.0).longValue()); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_PLAY, bundle); + + logFileView(claim.getPermanentUrl(), timeToStartMillis); + } + + private void logFileView(String url, long timeToStart) { + if (claim != null) { + LogFileViewTask task = new LogFileViewTask(url, claim, timeToStart, new GenericTaskHandler() { + @Override + public void beforeStart() { } + + @Override + public void onSuccess() { + claimEligibleRewards(); + } + + @Override + public void onError(Exception error) { } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + private void checkIsFollowing() { + if (claim != null && claim.getSigningChannel() != null) { + boolean isFollowing = Lbryio.isFollowing(claim.getSigningChannel()); + Context context = getContext(); + View root = getView(); + if (context != null && root != null) { + SolidIconView iconFollowUnfollow = root.findViewById(R.id.file_view_icon_follow_unfollow); + if (iconFollowUnfollow != null) { + iconFollowUnfollow.setText(isFollowing ? R.string.fa_heart_broken : R.string.fa_heart); + iconFollowUnfollow.setTextColor(ContextCompat.getColor(context, isFollowing ? R.color.foreground : R.color.red)); + } + } + } + } + + private void claimEligibleRewards() { + // attempt to claim eligible rewards after viewing or playing a file (fail silently) + Context context = getContext(); + ClaimRewardTask firstStreamTask = new ClaimRewardTask(Reward.TYPE_FIRST_STREAM, null, null, context, eligibleRewardHandler); + ClaimRewardTask dailyViewTask = new ClaimRewardTask(Reward.TYPE_DAILY_VIEW, null, null, context, eligibleRewardHandler); + firstStreamTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + dailyViewTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private ClaimRewardTask.ClaimRewardHandler eligibleRewardHandler = new ClaimRewardTask.ClaimRewardHandler() { + @Override + public void onSuccess(double amountClaimed, String message) { + if (Helper.isNullOrEmpty(message)) { + message = getResources().getQuantityString( + R.plurals.claim_reward_message, + amountClaimed == 1 ? 1 : 2, + new DecimalFormat(Helper.LBC_CURRENCY_FORMAT_PATTERN).format(amountClaimed)); + } + View root = getView(); + Context context = getContext(); + if (root != null) { + Snackbar.make(root.findViewById(R.id.file_view_global_layout), message, Snackbar.LENGTH_LONG).show(); + } else if (context instanceof MainActivity) { + ((MainActivity) context).showMessage(message); + } + } + + @Override + public void onError(Exception error) { + // pass + } + }; + + private void checkIsFileComplete() { + if (claim == null) { + return; + } + View root = getView(); + if (root != null) { + if (claim.getFile() != null && claim.getFile().isCompleted()) { + Helper.setViewVisibility(root.findViewById(R.id.file_view_action_delete), View.VISIBLE); + Helper.setViewVisibility(root.findViewById(R.id.file_view_action_download), View.GONE); + } else { + Helper.setViewVisibility(root.findViewById(R.id.file_view_action_delete), View.GONE); + Helper.setViewVisibility(root.findViewById(R.id.file_view_action_download), View.VISIBLE); + } + + } + } + + private void hideFloatingWalletBalance() { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).hideFloatingWalletBalance(); + } + } + private void showFloatingWalletBalance() { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).showFloatingWalletBalance(); + } + } + + private void toggleCast() { + if (!MainActivity.castPlayer.isCastSessionAvailable()) { + showError(getString(R.string.no_cast_session_available)); + return; + } + + if (currentPlayer == MainActivity.appPlayer) { + setCurrentPlayer(MainActivity.castPlayer); + } else { + setCurrentPlayer(MainActivity.appPlayer); + } + } + + private void onDownloadAborted() { + downloadInProgress = false; + + if (claim != null) { + claim.setFile(null); + } + ((ImageView) getView().findViewById(R.id.file_view_action_download_icon)).setImageResource(R.drawable.ic_download); + Helper.setViewVisibility(getView().findViewById(R.id.file_view_download_progress), View.GONE); + Helper.setViewVisibility(getView().findViewById(R.id.file_view_unsupported_container), View.GONE); + + checkIsFileComplete(); + restoreMainActionButton(); + } + + private void restoreMainActionButton() { + getView().findViewById(R.id.file_view_main_action_loading).setVisibility(View.INVISIBLE); + getView().findViewById(R.id.file_view_main_action_button).setVisibility(View.VISIBLE); + } + + @Override + public void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) { + if (uri == null || outpoint == null || (fileInfoJson == null && !"abort".equals(downloadAction))) { + return; + } + onRelatedDownloadAction(downloadAction, uri, outpoint, fileInfoJson, progress); + if (claim == null || claim != null && !claim.getPermanentUrl().equalsIgnoreCase(uri)) { + return; + } + if ("abort".equals(downloadAction)) { + onDownloadAborted(); + return; + } + + View root = getView(); + if (root != null) { + ImageView downloadIconView = root.findViewById(R.id.file_view_action_download_icon); + ProgressBar downloadProgressView = root.findViewById(R.id.file_view_download_progress); + + try { + JSONObject fileInfo = new JSONObject(fileInfoJson); + LbryFile claimFile = LbryFile.fromJSONObject(fileInfo); + claim.setFile(claimFile); + + if (DownloadManager.ACTION_START.equals(downloadAction)) { + downloadInProgress = true; + Helper.setViewVisibility(downloadProgressView, View.VISIBLE); + downloadProgressView.setProgress(0); + downloadIconView.setImageResource(R.drawable.ic_stop); + } else if (DownloadManager.ACTION_UPDATE.equals(downloadAction)) { + // handle download updated + downloadInProgress = true; + Helper.setViewVisibility(downloadProgressView, View.VISIBLE); + downloadProgressView.setProgress(Double.valueOf(progress).intValue()); + downloadIconView.setImageResource(R.drawable.ic_stop); + } else if (DownloadManager.ACTION_COMPLETE.equals(downloadAction)) { + downloadInProgress = false; + downloadProgressView.setProgress(100); + Helper.setViewVisibility(downloadProgressView, View.GONE); + playOrViewMedia(); + } + checkIsFileComplete(); + } catch (JSONException ex) { + // invalid file info for download + } + } + } + + @Override + public void onClaimsFetched(List claims) { + checkOwnClaim(); + } + + private static class LbryWebViewClient extends WebViewClient { + private Context context; + public LbryWebViewClient(Context context) { + this.context = context; + } + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + Uri url = request.getUrl(); + if (context != null) { + Intent intent = new Intent(Intent.ACTION_VIEW, url); + context.startActivity(intent); + } + return true; + } + } + + private void onRelatedDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) { + if ("abort".equals(downloadAction)) { + if (relatedContentAdapter != null) { + relatedContentAdapter.clearFileForClaimOrUrl(outpoint, uri); + } + return; + } + + try { + JSONObject fileInfo = new JSONObject(fileInfoJson); + LbryFile claimFile = LbryFile.fromJSONObject(fileInfo); + String claimId = claimFile.getClaimId(); + if (relatedContentAdapter != null) { + relatedContentAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri); + } + } catch (JSONException ex) { + // invalid file info for download + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + View root = getView(); + if (root != null) { + float speed = item.getItemId() / 100.0f; + String speedString = String.format("%sx", new DecimalFormat("0.##").format(speed)); + PlayerView playerView = root.findViewById(R.id.file_view_exoplayer_view); + ((TextView) playerView.findViewById(R.id.player_playback_speed_label)).setText(speedString); + + if (MainActivity.appPlayer != null) { + PlaybackParameters params = new PlaybackParameters(speed); + MainActivity.appPlayer.setPlaybackParameters(params); + } + } + return true; + } + + @Override + public boolean shouldHideGlobalPlayer() { + return true; + } + + private void checkOwnClaim() { + if (claim != null) { + boolean isOwnClaim = Lbry.ownClaims.contains(claim); + View root = getView(); + if (root != null) { + Helper.setViewVisibility(root.findViewById(R.id.file_view_action_download), isOwnClaim ? View.GONE : View.VISIBLE); + Helper.setViewVisibility(root.findViewById(R.id.file_view_action_report), isOwnClaim ? View.GONE : View.VISIBLE); + Helper.setViewVisibility(root.findViewById(R.id.file_view_action_edit), isOwnClaim ? View.VISIBLE : View.GONE); + Helper.setViewVisibility(root.findViewById(R.id.file_view_action_delete), isOwnClaim ? View.VISIBLE : View.GONE); + } + } + } + + private static class StreamLoadErrorPolicy extends DefaultLoadErrorHandlingPolicy { + @Override + public long getRetryDelayMsFor(int dataType, long loadDurationMs, IOException exception, int errorCount) { + return exception instanceof ParserException + || exception instanceof FileNotFoundException + || exception instanceof Loader.UnexpectedLoaderException + ? C.TIME_UNSET + : Math.min((errorCount - 1) * 1000, 5000); + } + + @Override + public int getMinimumLoadableRetryCount(int dataType) { + return Integer.MAX_VALUE; + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/findcontent/FollowingFragment.java b/app/src/main/java/io/lbry/browser/ui/findcontent/FollowingFragment.java new file mode 100644 index 00000000..de7052ca --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/findcontent/FollowingFragment.java @@ -0,0 +1,805 @@ +package io.lbry.browser.ui.findcontent; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.ChannelFilterListAdapter; +import io.lbry.browser.adapter.ClaimListAdapter; +import io.lbry.browser.adapter.SuggestedChannelGridAdapter; +import io.lbry.browser.dialog.ContentFromDialogFragment; +import io.lbry.browser.dialog.ContentSortDialogFragment; +import io.lbry.browser.dialog.DiscoverDialogFragment; +import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.listener.DownloadActionListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.tasks.claim.ClaimSearchResultHandler; +import io.lbry.browser.tasks.lbryinc.ChannelSubscribeTask; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimSearchTask; +import io.lbry.browser.tasks.lbryinc.FetchSubscriptionsTask; +import io.lbry.browser.tasks.claim.ResolveTask; +import io.lbry.browser.listener.ChannelItemSelectionListener; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; +import io.lbry.browser.utils.Predefined; + +public class FollowingFragment extends BaseFragment implements + FetchSubscriptionsTask.FetchSubscriptionsHandler, + ChannelItemSelectionListener, + DownloadActionListener, + SharedPreferences.OnSharedPreferenceChangeListener { + + public static boolean resetClaimSearchContent; + private static final int SUGGESTED_PAGE_SIZE = 45; + private static final int MIN_SUGGESTED_SUBSCRIBE_COUNT = 5; + + private DiscoverDialogFragment discoverDialog; + private List excludeChannelIdsForDiscover; + private MaterialButton suggestedDoneButton; + private TextView titleView; + private TextView infoView; + private RecyclerView horizontalChannelList; + private RecyclerView suggestedChannelGrid; + private RecyclerView contentList; + private ProgressBar bigContentLoading; + private ProgressBar contentLoading; + private ProgressBar channelListLoading; + private View layoutSortContainer; + private View sortLink; + private TextView sortLinkText; + private View contentFromLink; + private TextView contentFromLinkText; + private View discoverLink; + private int currentSortBy; + private int currentContentFrom; + private String contentReleaseTime; + private List contentSortOrder; + private boolean contentClaimSearchLoading = false; + private boolean suggestedClaimSearchLoading = false; + private View noContentView; + private boolean subscriptionsShown; + + private List queuedContentPages = new ArrayList<>(); + private List queuedSuggestedPages = new ArrayList<>(); + + private int currentSuggestedPage = 0; + private int currentClaimSearchPage; + private boolean suggestedHasReachedEnd; + private boolean contentHasReachedEnd; + private boolean contentPendingFetch = false; + private int numSuggestedSelected; + + // adapters + private SuggestedChannelGridAdapter suggestedChannelAdapter; + private ChannelFilterListAdapter channelFilterListAdapter; + private ClaimListAdapter contentListAdapter; + + private List channelIds; + private List channelUrls; + private List subscriptionsList; + private List suggestedChannels; + private ClaimSearchTask suggestedChannelClaimSearchTask; + private ClaimSearchTask contentClaimSearchTask; + private boolean loadingSuggested; + private boolean loadingContent; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_following, container, false); + + // Following page is sorted by new by default, past week if sort is top + currentSortBy = ContentSortDialogFragment.ITEM_SORT_BY_NEW; + currentContentFrom = ContentFromDialogFragment.ITEM_FROM_PAST_WEEK; + + titleView = root.findViewById(R.id.following_page_title); + infoView = root.findViewById(R.id.following_page_info); + horizontalChannelList = root.findViewById(R.id.following_channel_list); + layoutSortContainer = root.findViewById(R.id.following_filter_container); + sortLink = root.findViewById(R.id.following_sort_link); + sortLinkText = root.findViewById(R.id.following_sort_link_text); + contentFromLink = root.findViewById(R.id.following_time_link); + contentFromLinkText = root.findViewById(R.id.following_time_link_text); + suggestedChannelGrid = root.findViewById(R.id.following_suggested_grid); + suggestedDoneButton = root.findViewById(R.id.following_suggested_done_button); + contentList = root.findViewById(R.id.following_content_list); + bigContentLoading = root.findViewById(R.id.following_main_progress); + contentLoading = root.findViewById(R.id.following_content_progress); + channelListLoading = root.findViewById(R.id.following_channel_load_progress); + discoverLink = root.findViewById(R.id.following_discover_link); + noContentView = root.findViewById(R.id.following_no_claim_search_content); + + Context context = getContext(); + GridLayoutManager glm = new GridLayoutManager(context, 3); + suggestedChannelGrid.setLayoutManager(glm); + suggestedChannelGrid.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + if (suggestedClaimSearchLoading) { + return; + } + + GridLayoutManager lm = (GridLayoutManager) recyclerView.getLayoutManager(); + if (lm != null) { + int visibleItemCount = lm.getChildCount(); + int totalItemCount = lm.getItemCount(); + int pastVisibleItems = lm.findFirstVisibleItemPosition(); + if (pastVisibleItems + visibleItemCount >= totalItemCount) { + if (!suggestedHasReachedEnd) { + // load more + currentSuggestedPage++; + fetchSuggestedChannels(); + } + } + } + } + }); + + LinearLayoutManager cllm = new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false); + horizontalChannelList.setLayoutManager(cllm); + + LinearLayoutManager llm = new LinearLayoutManager(context); + contentList.setLayoutManager(llm); + contentList.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + if (contentClaimSearchLoading) { + return; + } + LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager(); + if (lm != null) { + int visibleItemCount = lm.getChildCount(); + int totalItemCount = lm.getItemCount(); + int pastVisibleItems = lm.findFirstVisibleItemPosition(); + if (pastVisibleItems + visibleItemCount >= totalItemCount) { + if (!contentHasReachedEnd) { + // load more + currentClaimSearchPage++; + fetchClaimSearchContent(); + } + } + } + } + }); + + suggestedDoneButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + int selected = suggestedChannelAdapter == null ? 0 : suggestedChannelAdapter.getSelectedCount(); + int remaining = MIN_SUGGESTED_SUBSCRIBE_COUNT - selected; + if (remaining == MIN_SUGGESTED_SUBSCRIBE_COUNT) { + Snackbar.make(getView(), R.string.select_five_subscriptions, Snackbar.LENGTH_LONG).show(); + } else { + fetchSubscriptions(); + showSubscribedContent(); + fetchAndResolveChannelList(); + } + } + }); + + sortLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ContentSortDialogFragment dialog = ContentSortDialogFragment.newInstance(); + dialog.setCurrentSortByItem(currentSortBy); + dialog.setSortByListener(new ContentSortDialogFragment.SortByListener() { + @Override + public void onSortByItemSelected(int sortBy) { + onSortByChanged(sortBy); + } + }); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + dialog.show(activity.getSupportFragmentManager(), ContentSortDialogFragment.TAG); + } + } + }); + contentFromLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ContentFromDialogFragment dialog = ContentFromDialogFragment.newInstance(); + dialog.setCurrentFromItem(currentContentFrom); + dialog.setContentFromListener(new ContentFromDialogFragment.ContentFromListener() { + @Override + public void onContentFromItemSelected(int contentFromItem) { + onContentFromChanged(contentFromItem); + } + }); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + dialog.show(activity.getSupportFragmentManager(), ContentFromDialogFragment.TAG); + } + } + }); + discoverLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + buildChannelIdsAndUrls(); + currentSuggestedPage = 1; + discoverDialog = DiscoverDialogFragment.newInstance(); + discoverDialog.setAdapter(suggestedChannelAdapter); + discoverDialog.setDialogActionsListener(new DiscoverDialogFragment.DiscoverDialogListener() { + @Override + public void onScrollEndReached() { + if (suggestedClaimSearchLoading) { + return; + } + currentSuggestedPage++; + fetchSuggestedChannels(); + } + @Override + public void onCancel() { + discoverDialog = null; + excludeChannelIdsForDiscover = null; + if (suggestedChannelAdapter != null) { + suggestedChannelAdapter.clearItems(); + } + } + @Override + public void onResume() { + if (suggestedChannelAdapter == null || suggestedChannelAdapter.getItemCount() == 0) { + discoverDialog.setLoading(true); + fetchSuggestedChannels(); + } + } + }); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + discoverDialog.show(activity.getSupportFragmentManager(), DiscoverDialogFragment.TAG); + } + } + }); + + return root; + } + + private void onContentFromChanged(int contentFrom) { + currentContentFrom = contentFrom; + + // rebuild options and search + updateContentFromLinkText(); + contentReleaseTime = Helper.buildReleaseTime(currentContentFrom); + fetchClaimSearchContent(true); + } + + private void onSortByChanged(int sortBy) { + currentSortBy = sortBy; + + // rebuild options and search + Helper.setViewVisibility(contentFromLink, currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ? View.VISIBLE : View.GONE); + currentContentFrom = currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ? + (currentContentFrom == 0 ? ContentFromDialogFragment.ITEM_FROM_PAST_WEEK : currentContentFrom) : 0; + + updateSortByLinkText(); + contentSortOrder = Helper.buildContentSortOrder(currentSortBy); + contentReleaseTime = Helper.buildReleaseTime(currentContentFrom); + fetchClaimSearchContent(true); + } + + private void updateSortByLinkText() { + int stringResourceId = -1; + switch (currentSortBy) { + case ContentSortDialogFragment.ITEM_SORT_BY_NEW: default: stringResourceId = R.string.new_text; break; + case ContentSortDialogFragment.ITEM_SORT_BY_TOP: stringResourceId = R.string.top; break; + case ContentSortDialogFragment.ITEM_SORT_BY_TRENDING: stringResourceId = R.string.trending; break; + } + + Helper.setViewText(sortLinkText, stringResourceId); + } + + private void updateContentFromLinkText() { + int stringResourceId = -1; + switch (currentContentFrom) { + case ContentFromDialogFragment.ITEM_FROM_PAST_24_HOURS: stringResourceId = R.string.past_24_hours; break; + case ContentFromDialogFragment.ITEM_FROM_PAST_WEEK: default: stringResourceId = R.string.past_week; break; + case ContentFromDialogFragment.ITEM_FROM_PAST_MONTH: stringResourceId = R.string.past_month; break; + case ContentFromDialogFragment.ITEM_FROM_PAST_YEAR: stringResourceId = R.string.past_year; break; + case ContentFromDialogFragment.ITEM_FROM_ALL_TIME: stringResourceId = R.string.all_time; break; + } + + Helper.setViewText(contentFromLinkText, stringResourceId); + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + Helper.setWunderbarValue(null, context); + PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Subscriptions", "Subscriptions"); + activity.addDownloadActionListener(this); + } + + // check if subscriptions exist + if (suggestedChannelAdapter != null) { + showSuggestedChannels(); + if (suggestedChannelGrid != null) { + suggestedChannelGrid.setAdapter(suggestedChannelAdapter); + } + } + + if (Lbryio.subscriptions != null && Lbryio.subscriptions.size() > 0) { + fetchLoadedSubscriptions(true); + } else { + fetchSubscriptions(); + } + } + public void onPause() { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).removeDownloadActionListener(this); + } + PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); + } + public void fetchLoadedSubscriptions(boolean showSubscribed) { + subscriptionsList = new ArrayList<>(Lbryio.subscriptions); + buildChannelIdsAndUrls(); + if (Lbryio.cacheResolvedSubscriptions.size() > 0) { + updateChannelFilterListAdapter(Lbryio.cacheResolvedSubscriptions, resetClaimSearchContent); + } else { + fetchAndResolveChannelList(); + } + + fetchClaimSearchContent(resetClaimSearchContent); + resetClaimSearchContent = false; + if (showSubscribed && subscriptionsList.size() > 0) { + showSubscribedContent(); + } + } + + public void loadFollowing() { + // wrapper to just re-fetch subscriptions (upon user sign in, for example) + fetchSubscriptions(); + } + + private void fetchSubscriptions() { + FetchSubscriptionsTask task = new FetchSubscriptionsTask(getContext(), channelListLoading, this); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private Map buildSuggestedOptions() { + Context context = getContext(); + boolean canShowMatureContent = false; + if (context != null) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + } + + return Lbry.buildClaimSearchOptions( + Claim.TYPE_CHANNEL, + null, + canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS), + null, + excludeChannelIdsForDiscover, + Arrays.asList(Claim.ORDER_BY_EFFECTIVE_AMOUNT), + null, + currentSuggestedPage == 0 ? 1 : currentSuggestedPage, + SUGGESTED_PAGE_SIZE); + } + + private Map buildContentOptions() { + Context context = getContext(); + boolean canShowMatureContent = false; + if (context != null) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + } + + return Lbry.buildClaimSearchOptions( + Claim.TYPE_STREAM, + null, + canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS), + getChannelIds(), + null, + getContentSortOrder(), + contentReleaseTime, + currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage, + Helper.CONTENT_PAGE_SIZE); + } + + private List getChannelIds() { + if (channelFilterListAdapter != null) { + Claim selected = channelFilterListAdapter.getSelectedItem(); + if (selected != null) { + return Arrays.asList(selected.getClaimId()); + } + } + + return channelIds; + } + + private List getContentSortOrder() { + if (contentSortOrder == null) { + return Arrays.asList(Claim.ORDER_BY_RELEASE_TIME); + } + return contentSortOrder; + } + + private void showSuggestedChannels() { + Helper.setViewText(titleView, R.string.find_channels_to_follow); + + Helper.setViewVisibility(horizontalChannelList, View.GONE); + Helper.setViewVisibility(contentList, View.GONE); + Helper.setViewVisibility(infoView, View.VISIBLE); + Helper.setViewVisibility(layoutSortContainer, View.GONE); + Helper.setViewVisibility(suggestedChannelGrid, View.VISIBLE); + Helper.setViewVisibility(suggestedDoneButton, View.VISIBLE); + + updateSuggestedDoneButtonText(); + } + + private void showSubscribedContent() { + subscriptionsShown = true; + Helper.setViewText(titleView, R.string.channels_you_follow); + + Helper.setViewVisibility(horizontalChannelList, View.VISIBLE); + Helper.setViewVisibility(contentList, View.VISIBLE); + Helper.setViewVisibility(infoView, View.GONE); + Helper.setViewVisibility(layoutSortContainer, View.VISIBLE); + Helper.setViewVisibility(suggestedChannelGrid, View.GONE); + Helper.setViewVisibility(suggestedDoneButton, View.GONE); + } + + private void buildChannelIdsAndUrls() { + channelIds = new ArrayList<>(); + channelUrls = new ArrayList<>(); + if (subscriptionsList != null) { + for (Subscription subscription : subscriptionsList) { + try { + String url = subscription.getUrl(); + LbryUri uri = LbryUri.parse(url); + String claimId = uri.getClaimId(); + channelIds.add(claimId); + channelUrls.add(url); + } catch (LbryUriException ex) { + // pass + } + } + } + + excludeChannelIdsForDiscover = channelIds != null ? new ArrayList<>(channelIds) : null; + } + + private void fetchAndResolveChannelList() { + buildChannelIdsAndUrls(); + if (channelIds.size() > 0) { + ResolveTask resolveSubscribedTask = new ResolveTask(channelUrls, Lbry.LBRY_TV_CONNECTION_STRING, channelListLoading, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + updateChannelFilterListAdapter(claims, true); + Lbryio.cacheResolvedSubscriptions = claims; + } + + @Override + public void onError(Exception error) { + fetchAndResolveChannelList(); + } + }); + resolveSubscribedTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + + fetchClaimSearchContent(); + } + } + + private View getLoadingView() { + return (contentListAdapter == null || contentListAdapter.getItemCount() == 0) ? bigContentLoading : contentLoading; + } + + private void updateChannelFilterListAdapter(List resolvedSubs, boolean reset) { + if (channelFilterListAdapter == null) { + channelFilterListAdapter = new ChannelFilterListAdapter(getContext()); + channelFilterListAdapter.setListener(new ChannelItemSelectionListener() { + @Override + public void onChannelItemSelected(Claim claim) { + if (contentClaimSearchTask != null && contentClaimSearchTask.getStatus() != AsyncTask.Status.FINISHED) { + contentClaimSearchTask.cancel(true); + } + if (contentListAdapter != null) { + contentListAdapter.clearItems(); + } + currentClaimSearchPage = 1; + contentClaimSearchLoading = false; + fetchClaimSearchContent(); + } + + @Override + public void onChannelItemDeselected(Claim claim) { + + } + + @Override + public void onChannelSelectionCleared() { + if (contentClaimSearchTask != null && contentClaimSearchTask.getStatus() != AsyncTask.Status.FINISHED) { + contentClaimSearchTask.cancel(true); + } + if (contentListAdapter != null) { + contentListAdapter.clearItems(); + } + currentClaimSearchPage = 1; + contentClaimSearchLoading = false; + fetchClaimSearchContent(); + } + }); + } + + if (horizontalChannelList != null && horizontalChannelList.getAdapter() == null) { + horizontalChannelList.setAdapter(channelFilterListAdapter); + } + if (reset) { + channelFilterListAdapter.clearClaims(); + channelFilterListAdapter.setSelectedItem(null); + } + channelFilterListAdapter.addClaims(resolvedSubs); + } + + private void fetchClaimSearchContent() { + fetchClaimSearchContent(false); + } + + private void fetchClaimSearchContent(boolean reset) { + if (reset && contentListAdapter != null) { + contentListAdapter.clearItems(); + currentClaimSearchPage = 1; + } + + contentClaimSearchLoading = true; + Helper.setViewVisibility(noContentView, View.GONE); + Map claimSearchOptions = buildContentOptions(); + contentClaimSearchTask = new ClaimSearchTask(claimSearchOptions, Lbry.LBRY_TV_CONNECTION_STRING, getLoadingView(), new ClaimSearchResultHandler() { + @Override + public void onSuccess(List claims, boolean hasReachedEnd) { + if (contentListAdapter == null) { + contentListAdapter = new ClaimListAdapter(claims, getContext()); + contentListAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() { + @Override + public void onClaimClicked(Claim claim) { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (claim.getName().startsWith("@")) { + // channel claim + activity.openChannelClaim(claim); + } else { + activity.openFileClaim(claim); + } + } + } + }); + } else { + contentListAdapter.addItems(claims); + } + + if (contentList != null && contentList.getAdapter() == null) { + contentList.setAdapter(contentListAdapter); + } + + contentHasReachedEnd = hasReachedEnd; + contentClaimSearchLoading = false; + checkNoContent(false); + } + + @Override + public void onError(Exception error) { + contentClaimSearchLoading = false; + checkNoContent(false); + } + }); + contentClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void updateSuggestedDoneButtonText() { + int selected = suggestedChannelAdapter == null ? 0 : suggestedChannelAdapter.getSelectedCount(); + int remaining = MIN_SUGGESTED_SUBSCRIBE_COUNT - selected; + String buttonText = remaining <= 0 ? getString(R.string.done) : getString(R.string.n_remaining, remaining); + if (suggestedDoneButton != null) { + suggestedDoneButton.setText(buttonText); + } + } + + private void fetchSuggestedChannels() { + if (suggestedClaimSearchLoading) { + return; + } + + suggestedClaimSearchLoading = true; + if (discoverDialog != null) { + discoverDialog.setLoading(true); + } + + Helper.setViewVisibility(noContentView, View.GONE); + suggestedChannelClaimSearchTask = new ClaimSearchTask( + buildSuggestedOptions(), + Lbry.LBRY_TV_CONNECTION_STRING, + suggestedChannelAdapter == null || suggestedChannelAdapter.getItemCount() == 0 ? bigContentLoading : contentLoading, + new ClaimSearchResultHandler() { + @Override + public void onSuccess(List claims, boolean hasReachedEnd) { + suggestedHasReachedEnd = hasReachedEnd; + suggestedClaimSearchLoading = false; + if (discoverDialog != null) { + discoverDialog.setLoading(false); + } + + if (suggestedChannelAdapter == null) { + suggestedChannelAdapter = new SuggestedChannelGridAdapter(claims, getContext()); + suggestedChannelAdapter.setListener(FollowingFragment.this); + if (suggestedChannelGrid != null) { + suggestedChannelGrid.setAdapter(suggestedChannelAdapter); + } + if (discoverDialog != null) { + discoverDialog.setAdapter(suggestedChannelAdapter); + } + } else { + suggestedChannelAdapter.addClaims(claims); + } + + if (discoverDialog == null || !discoverDialog.isVisible()) { + checkNoContent(true); + } + } + + @Override + public void onError(Exception error) { + suggestedClaimSearchLoading = false; + if (discoverDialog != null) { + discoverDialog.setLoading(false); + } + if (discoverDialog == null || !discoverDialog.isVisible()) { + checkNoContent(true); + } + } + }); + + suggestedChannelClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + // handler methods + public void onSuccess(List subscriptions) { + if (subscriptions.size() == 0) { + // fresh start + // TODO: Only do this if there are no local subscriptions stored + currentSuggestedPage = 1; + buildSuggestedOptions(); + loadingSuggested = true; + loadingContent = false; + + fetchSuggestedChannels(); + showSuggestedChannels(); + } else { + Lbryio.subscriptions = subscriptions; + subscriptionsList = new ArrayList<>(subscriptions); + showSubscribedContent(); + fetchAndResolveChannelList(); + } + } + + public void onError(Exception exception) { + + } + + public void onChannelItemSelected(Claim claim) { + // subscribe + Subscription subscription = Subscription.fromClaim(claim); + String channelClaimId = claim.getClaimId(); + + ChannelSubscribeTask task = new ChannelSubscribeTask(getContext(), channelClaimId, subscription, false, new ChannelSubscribeTask.ChannelSubscribeHandler() { + @Override + public void onSuccess() { + Lbryio.addSubscription(subscription); + Lbryio.addCachedResolvedSubscription(claim); + resetClaimSearchContent = true; + fetchLoadedSubscriptions(subscriptionsShown); + + saveSharedUserState(); + } + + @Override + public void onError(Exception error) { } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + updateSuggestedDoneButtonText(); + } + public void onChannelItemDeselected(Claim claim) { + // unsubscribe + Subscription subscription = Subscription.fromClaim(claim); + String channelClaimId = claim.getClaimId(); + ChannelSubscribeTask task = new ChannelSubscribeTask(getContext(), channelClaimId, subscription, true, new ChannelSubscribeTask.ChannelSubscribeHandler() { + @Override + public void onSuccess() { + Lbryio.removeSubscription(subscription); + Lbryio.removeCachedResolvedSubscription(claim); + resetClaimSearchContent = true; + fetchLoadedSubscriptions(subscriptionsShown); + + saveSharedUserState(); + } + + @Override + public void onError(Exception error) { + + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + updateSuggestedDoneButtonText(); + } + public void onChannelSelectionCleared() { + + } + + private void checkNoContent(boolean suggested) { + RecyclerView.Adapter adapter = suggested ? suggestedChannelAdapter : contentListAdapter; + boolean noContent = adapter == null || adapter.getItemCount() == 0; + Helper.setViewVisibility(noContentView, noContent ? View.VISIBLE : View.GONE); + } + + private void saveSharedUserState() { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).saveSharedUserState(); + } + } + + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (key.equalsIgnoreCase(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT)) { + fetchClaimSearchContent(true); + } + } + + public void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) { + if ("abort".equals(downloadAction)) { + if (contentListAdapter != null) { + contentListAdapter.clearFileForClaimOrUrl(outpoint, uri); + } + return; + } + + try { + JSONObject fileInfo = new JSONObject(fileInfoJson); + LbryFile claimFile = LbryFile.fromJSONObject(fileInfo); + String claimId = claimFile.getClaimId(); + if (contentListAdapter != null) { + contentListAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri); + } + } catch (JSONException ex) { + // invalid file info for download + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/findcontent/SearchFragment.java b/app/src/main/java/io/lbry/browser/ui/findcontent/SearchFragment.java new file mode 100644 index 00000000..901d57fa --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/findcontent/SearchFragment.java @@ -0,0 +1,321 @@ +package io.lbry.browser.ui.findcontent; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.ClaimListAdapter; +import io.lbry.browser.listener.DownloadActionListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.ClaimCacheKey; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.model.NavMenuItem; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimSearchResultHandler; +import io.lbry.browser.tasks.LighthouseSearchTask; +import io.lbry.browser.tasks.claim.ResolveTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.ui.publish.PublishFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.LbryUri; +import lombok.Setter; + +public class SearchFragment extends BaseFragment implements + ClaimListAdapter.ClaimListItemListener, DownloadActionListener, SharedPreferences.OnSharedPreferenceChangeListener { + private static final int PAGE_SIZE = 25; + + private ClaimListAdapter resultListAdapter; + private ProgressBar loadingView; + private RecyclerView resultList; + private TextView noQueryView; + private TextView noResultsView; + + @Setter + private String currentQuery; + private boolean searchLoading; + private boolean contentHasReachedEnd; + private int currentFrom; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_search, container, false); + + loadingView = root.findViewById(R.id.search_loading); + noQueryView = root.findViewById(R.id.search_no_query); + noResultsView = root.findViewById(R.id.search_no_results); + + resultList = root.findViewById(R.id.search_result_list); + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + resultList.setLayoutManager(llm); + resultList.setAdapter(resultListAdapter); + resultList.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + if (searchLoading) { + return; + } + + LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager(); + if (lm != null) { + int visibleItemCount = lm.getChildCount(); + int totalItemCount = lm.getItemCount(); + int pastVisibleItems = lm.findFirstVisibleItemPosition(); + if (pastVisibleItems + visibleItemCount >= totalItemCount) { + if (!contentHasReachedEnd) { + // load more + int newFrom = currentFrom + PAGE_SIZE; + search(currentQuery, newFrom); + } + } + } + } + }); + + return root; + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + Helper.setWunderbarValue(currentQuery, context); + PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Search", "Search"); + activity.addDownloadActionListener(this); + } + if (!Helper.isNullOrEmpty(currentQuery)) { + logSearch(currentQuery); + search(currentQuery, currentFrom); + } else { + noQueryView.setVisibility(View.VISIBLE); + noResultsView.setVisibility(View.GONE); + } + } + + public void onPause() { + Context context = getContext(); + if (context != null) { + ((MainActivity) context).removeDownloadActionListener(this); + } + PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); + } + + private boolean checkQuery(String query) { + if (!Helper.isNullOrEmpty(query) && !query.equalsIgnoreCase(currentQuery)) { + // new query, reset values + currentFrom = 0; + currentQuery = query; + if (resultListAdapter != null) { + resultListAdapter.clearItems(); + } + return true; + } + + return false; + } + + private Claim buildFeaturedItem(String query) { + Claim claim = new Claim(); + claim.setName(query); + claim.setFeatured(true); + claim.setUnresolved(true); + claim.setConfirmations(1); + return claim; + } + + private String buildVanityUrl(String query) { + LbryUri url = new LbryUri(); + url.setClaimName(query); + return url.toString(); + } + + private void resolveFeaturedItem(String vanityUrl) { + final ClaimCacheKey key = new ClaimCacheKey(); + key.setUrl(vanityUrl); + if (Lbry.claimCache.containsKey(key)) { + Claim cachedClaim = Lbry.claimCache.get(key); + updateFeaturedItemFromResolvedClaim(cachedClaim); + return; + } + + ResolveTask task = new ResolveTask(vanityUrl, Lbry.LBRY_TV_CONNECTION_STRING, null, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + if (claims.size() > 0 && !Helper.isNullOrEmpty(claims.get(0).getClaimId())) { + Claim resolved = claims.get(0); + Lbry.claimCache.put(key, resolved); + updateFeaturedItemFromResolvedClaim(resolved); + } + } + + @Override + public void onError(Exception error) { + + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void updateFeaturedItemFromResolvedClaim(Claim resolved) { + if (resultListAdapter != null) { + Claim unresolved = resultListAdapter.getFeaturedItem(); + + Context context = getContext(); + boolean canShowMatureContent = false; + if (context != null) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + } + if (resolved.isMature() && !canShowMatureContent) { + resultListAdapter.removeFeaturedItem(); + } else { + // only set the values we need + unresolved.setClaimId(resolved.getClaimId()); + unresolved.setName(resolved.getName()); + unresolved.setTimestamp(resolved.getTimestamp()); + unresolved.setValueType(resolved.getValueType()); + unresolved.setPermanentUrl(resolved.getPermanentUrl()); + unresolved.setValue(resolved.getValue()); + unresolved.setSigningChannel(resolved.getSigningChannel()); + unresolved.setUnresolved(false); + unresolved.setConfirmations(resolved.getConfirmations()); + } + + resultListAdapter.notifyDataSetChanged(); + } + } + + private void logSearch(String query) { + Bundle bundle = new Bundle(); + bundle.putString("query", query); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_SEARCH, bundle); + } + + public void search(String query, int from) { + boolean queryChanged = checkQuery(query); + if (!queryChanged && from > 0) { + currentFrom = from; + } + + if (queryChanged) { + logSearch(query); + } + + searchLoading = true; + Context context = getContext(); + boolean canShowMatureContent = false; + if (context != null) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + } + + LighthouseSearchTask task = new LighthouseSearchTask( + currentQuery, PAGE_SIZE, currentFrom, canShowMatureContent, null, loadingView, new ClaimSearchResultHandler() { + @Override + public void onSuccess(List claims, boolean hasReachedEnd) { + contentHasReachedEnd = hasReachedEnd; + searchLoading = false; + + if (resultListAdapter == null) { + resultListAdapter = new ClaimListAdapter(claims, getContext()); + resultListAdapter.addFeaturedItem(buildFeaturedItem(query)); + resolveFeaturedItem(buildVanityUrl(query)); + resultListAdapter.setListener(SearchFragment.this); + if (resultList != null) { + resultList.setAdapter(resultListAdapter); + } + } else { + resultListAdapter.addItems(claims); + } + + int itemCount = resultListAdapter.getItemCount(); + noQueryView.setVisibility(View.GONE); + noResultsView.setVisibility(itemCount == 0 ? View.VISIBLE : View.GONE); + noResultsView.setText(getString(R.string.search_no_results, currentQuery)); + } + + @Override + public void onError(Exception error) { + int itemCount = resultListAdapter == null ? 0 : resultListAdapter.getItemCount(); + noQueryView.setVisibility(View.GONE); + noResultsView.setVisibility(itemCount == 0 ? View.VISIBLE : View.GONE); + noResultsView.setText(getString(R.string.search_no_results, currentQuery)); + searchLoading = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void onClaimClicked(Claim claim) { + if (Helper.isNullOrEmpty(claim.getName())) { + // never should happen, but if it does, do nothing + return; + } + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (claim.isUnresolved()) { + // open the publish page + Map params = new HashMap<>(); + params.put("suggestedUrl", claim.getName()); + activity.openFragment(PublishFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH, params); + } else if (claim.getName().startsWith("@")) { + activity.openChannelUrl(claim.getPermanentUrl()); + } else { + // not a channel + activity.openFileClaim(claim); + } + } + } + + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (key.equalsIgnoreCase(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT)) { + search(currentQuery, currentFrom); + } + } + + public void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) { + if ("abort".equals(downloadAction)) { + if (resultListAdapter != null) { + resultListAdapter.clearFileForClaimOrUrl(outpoint, uri); + } + return; + } + + try { + JSONObject fileInfo = new JSONObject(fileInfoJson); + LbryFile claimFile = LbryFile.fromJSONObject(fileInfo); + String claimId = claimFile.getClaimId(); + if (resultListAdapter != null) { + resultListAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri); + } + } catch (JSONException ex) { + // invalid file info for download + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/library/LibraryFragment.java b/app/src/main/java/io/lbry/browser/ui/library/LibraryFragment.java new file mode 100644 index 00000000..49377b2a --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/library/LibraryFragment.java @@ -0,0 +1,741 @@ +package io.lbry.browser.ui.library; + +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Typeface; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.view.ActionMode; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.snackbar.Snackbar; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.ClaimListAdapter; +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.listener.DownloadActionListener; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.listener.SelectionModeListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.model.ViewHistory; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimListTask; +import io.lbry.browser.tasks.claim.ClaimSearchResultHandler; +import io.lbry.browser.tasks.claim.PurchaseListTask; +import io.lbry.browser.tasks.claim.ResolveTask; +import io.lbry.browser.tasks.file.BulkDeleteFilesTask; +import io.lbry.browser.tasks.file.FileListTask; +import io.lbry.browser.tasks.localdata.FetchViewHistoryTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.LbryUri; + +public class LibraryFragment extends BaseFragment implements + ActionMode.Callback, DownloadActionListener, SelectionModeListener, SdkStatusListener { + + private static final int FILTER_DOWNLOADS = 1; + private static final int FILTER_PURCHASES = 2; + private static final int FILTER_HISTORY = 3; + private static final int PAGE_SIZE = 50; + + private ActionMode actionMode; + private int currentFilter; + private List currentFiles; + private View layoutSdkInitializing; + private RecyclerView contentList; + private ClaimListAdapter contentListAdapter; + private ProgressBar listLoading; + private TextView linkFilterDownloads; + private TextView linkFilterPurchases; + private TextView linkFilterHistory; + private View layoutListEmpty; + private TextView textListEmpty; + private int currentPage; + private Date lastDate; + private boolean listReachedEnd; + private boolean contentListLoading; + private boolean initialOwnClaimsFetched; + + private CardView cardStats; + private TextView linkStats; + private TextView linkHide; + private View viewStatsDistribution; + private View viewVideoStatsBar; + private View viewAudioStatsBar; + private View viewImageStatsBar; + private View viewOtherStatsBar; + private TextView textStatsTotalSize; + private TextView textStatsTotalSizeUnits; + private TextView textStatsVideoSize; + private TextView textStatsAudioSize; + private TextView textStatsImageSize; + private TextView textStatsOtherSize; + private View legendVideo; + private View legendAudio; + private View legendImage; + private View legendOther; + + private long totalBytes; + private long totalVideoBytes; + private long totalAudioBytes; + private long totalImageBytes; + private long totalOtherBytes; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_library, container, false); + + layoutSdkInitializing = root.findViewById(R.id.container_library_sdk_initializing); + + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + contentList = root.findViewById(R.id.library_list); + contentList.setLayoutManager(llm); + + listLoading = root.findViewById(R.id.library_list_loading); + linkFilterDownloads = root.findViewById(R.id.library_filter_link_downloads); + linkFilterPurchases = root.findViewById(R.id.library_filter_link_purchases); + linkFilterHistory = root.findViewById(R.id.library_filter_link_history); + + layoutListEmpty = root.findViewById(R.id.library_empty_container); + textListEmpty = root.findViewById(R.id.library_list_empty_text); + + currentFilter = FILTER_DOWNLOADS; + linkFilterDownloads.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showDownloads(); + } + }); + linkFilterPurchases.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showPurchases(); + } + }); + linkFilterHistory.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showHistory(); + } + }); + contentList.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + if (contentListLoading) { + return; + } + + LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager(); + if (lm != null) { + int visibleItemCount = lm.getChildCount(); + int totalItemCount = lm.getItemCount(); + int pastVisibleItems = lm.findFirstVisibleItemPosition(); + if (pastVisibleItems + visibleItemCount >= totalItemCount) { + if (!listReachedEnd) { + // load more + if (currentFilter == FILTER_DOWNLOADS) { + currentPage++; + fetchDownloads(); + } else if (currentFilter == FILTER_HISTORY) { + fetchHistory(); + } + } + } + } + } + }); + + // stats + linkStats = root.findViewById(R.id.library_show_stats); + linkHide = root.findViewById(R.id.library_hide_stats); + cardStats = root.findViewById(R.id.library_storage_stats_card); + viewStatsDistribution = root.findViewById(R.id.library_storage_stat_distribution); + viewVideoStatsBar = root.findViewById(R.id.library_storage_stat_video_bar); + viewAudioStatsBar = root.findViewById(R.id.library_storage_stat_audio_bar); + viewImageStatsBar = root.findViewById(R.id.library_storage_stat_image_bar); + viewOtherStatsBar = root.findViewById(R.id.library_storage_stat_other_bar); + textStatsTotalSize = root.findViewById(R.id.library_storage_stat_used); + textStatsTotalSizeUnits = root.findViewById(R.id.library_storage_stat_unit); + textStatsVideoSize = root.findViewById(R.id.library_storage_stat_video_size); + textStatsAudioSize = root.findViewById(R.id.library_storage_stat_audio_size); + textStatsImageSize = root.findViewById(R.id.library_storage_stat_image_size); + textStatsOtherSize = root.findViewById(R.id.library_storage_stat_other_size); + legendVideo = root.findViewById(R.id.library_storage_legend_video); + legendAudio = root.findViewById(R.id.library_storage_legend_audio); + legendImage = root.findViewById(R.id.library_storage_legend_image); + legendOther = root.findViewById(R.id.library_storage_legend_other); + + linkStats.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + updateStats(); + cardStats.setVisibility(View.VISIBLE); + checkStatsLink(); + } + }); + linkHide.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + cardStats.setVisibility(View.GONE); + checkStatsLink(); + } + }); + + return root; + } + + public void onResume() { + super.onResume(); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Library", "Library"); + activity.addDownloadActionListener(this); + } + + layoutSdkInitializing.setVisibility( + !Lbry.SDK_READY && currentFilter == FILTER_DOWNLOADS ? View.VISIBLE : View.GONE); + if (!Lbry.SDK_READY) { + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.addSdkStatusListener(this); + } + } else { + onSdkReady(); + } + } + + public void onPause() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.removeSdkStatusListener(this); + activity.removeDownloadActionListener(this); + } + super.onPause(); + } + + public void onSdkReady() { + layoutSdkInitializing.setVisibility(View.GONE); + if (currentFilter == FILTER_DOWNLOADS) { + showDownloads(); + } else if (currentFilter == FILTER_HISTORY) { + showHistory(); + } else if (currentFilter == FILTER_PURCHASES) { + showPurchases(); + } + } + + private void showDownloads() { + currentFilter = FILTER_DOWNLOADS; + linkFilterDownloads.setTypeface(null, Typeface.BOLD); + linkFilterPurchases.setTypeface(null, Typeface.NORMAL); + linkFilterHistory.setTypeface(null, Typeface.NORMAL); + if (contentListAdapter != null) { + contentListAdapter.setHideFee(false); + contentListAdapter.clearItems(); + contentListAdapter.setCanEnterSelectionMode(true); + } + listReachedEnd = false; + + checkStatsLink(); + layoutSdkInitializing.setVisibility(Lbry.SDK_READY ? View.GONE : View.VISIBLE); + currentPage = 1; + if (Lbry.SDK_READY) { + if (!initialOwnClaimsFetched) { + fetchOwnClaimsAndShowDownloads(); + } else { + fetchDownloads(); + } + } + } + + private void fetchOwnClaimsAndShowDownloads() { + if (Lbry.ownClaims != null && Lbry.ownClaims.size() > 0) { + initialOwnClaimsFetched = true; + fetchDownloads(); + return; + } + + linkStats.setVisibility(View.INVISIBLE); + ClaimListTask task = new ClaimListTask(Arrays.asList(Claim.TYPE_STREAM, Claim.TYPE_REPOST), listLoading, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + Lbry.ownClaims = Helper.filterDeletedClaims(new ArrayList<>(claims)); + initialOwnClaimsFetched = true; + if (currentFilter == FILTER_DOWNLOADS) { + fetchDownloads(); + } + checkStatsLink(); + } + + @Override + public void onError(Exception error) { + initialOwnClaimsFetched = true; + checkStatsLink(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void showPurchases() { + currentFilter = FILTER_PURCHASES; + linkFilterDownloads.setTypeface(null, Typeface.NORMAL); + linkFilterPurchases.setTypeface(null, Typeface.BOLD); + linkFilterHistory.setTypeface(null, Typeface.NORMAL); + if (contentListAdapter != null) { + contentListAdapter.setHideFee(true); + contentListAdapter.clearItems(); + contentListAdapter.setCanEnterSelectionMode(true); + } + listReachedEnd = false; + + cardStats.setVisibility(View.GONE); + checkStatsLink(); + + layoutSdkInitializing.setVisibility(Lbry.SDK_READY ? View.GONE : View.VISIBLE); + currentPage = 1; + if (Lbry.SDK_READY) { + fetchPurchases(); + } + } + + private void showHistory() { + currentFilter = FILTER_HISTORY; + linkFilterDownloads.setTypeface(null, Typeface.NORMAL); + linkFilterPurchases.setTypeface(null, Typeface.NORMAL); + linkFilterHistory.setTypeface(null, Typeface.BOLD); + if (actionMode != null) { + actionMode.finish(); + } + if (contentListAdapter != null) { + contentListAdapter.setHideFee(false); + contentListAdapter.clearItems(); + contentListAdapter.setCanEnterSelectionMode(false); + } + listReachedEnd = false; + + cardStats.setVisibility(View.GONE); + checkStatsLink(); + + layoutSdkInitializing.setVisibility(View.GONE); + lastDate = null; + fetchHistory(); + } + + private void initContentListAdapter(List claims) { + contentListAdapter = new ClaimListAdapter(claims, getContext()); + contentListAdapter.setCanEnterSelectionMode(true); + contentListAdapter.setSelectionModeListener(this); + contentListAdapter.setHideFee(currentFilter != FILTER_PURCHASES); + contentListAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() { + @Override + public void onClaimClicked(Claim claim) { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) getContext(); + if (claim.getName().startsWith("@")) { + activity.openChannelUrl(claim.getPermanentUrl()); + } else { + activity.openFileUrl(claim.getPermanentUrl()); + } + } + } + }); + } + + private void fetchDownloads() { + contentListLoading = true; + Helper.setViewVisibility(linkStats, View.GONE); + Helper.setViewVisibility(layoutListEmpty, View.GONE); + FileListTask task = new FileListTask(currentPage, PAGE_SIZE, true, listLoading, new FileListTask.FileListResultHandler() { + @Override + public void onSuccess(List files, boolean hasReachedEnd) { + listReachedEnd = hasReachedEnd; + List filteredFiles = Helper.filterDownloads(files); + List claims = Helper.claimsFromFiles(filteredFiles); + + addFiles(filteredFiles); + updateStats(); + checkStatsLink(); + + if (contentListAdapter == null) { + initContentListAdapter(claims); + } else { + contentListAdapter.addItems(claims); + } + if (contentList.getAdapter() == null) { + contentList.setAdapter(contentListAdapter); + } + resolveMissingChannelNames(buildUrlsToResolve(claims)); + checkListEmpty(); + contentListLoading = false; + } + + @Override + public void onError(Exception error) { + // pass + checkStatsLink(); + checkListEmpty(); + contentListLoading = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void fetchPurchases() { + contentListLoading = true; + Helper.setViewVisibility(linkStats, View.GONE); + Helper.setViewVisibility(layoutListEmpty, View.GONE); + PurchaseListTask task = new PurchaseListTask(currentPage, PAGE_SIZE, listLoading, new ClaimSearchResultHandler() { + @Override + public void onSuccess(List claims, boolean hasReachedEnd) { + listReachedEnd = hasReachedEnd; + if (contentListAdapter == null) { + initContentListAdapter(claims); + } else { + contentListAdapter.addItems(claims); + } + if (contentList.getAdapter() == null) { + contentList.setAdapter(contentListAdapter); + } + checkListEmpty(); + contentListLoading = false; + } + + @Override + public void onError(Exception error) { + checkStatsLink(); + checkListEmpty(); + contentListLoading = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void fetchHistory() { + contentListLoading = true; + Helper.setViewVisibility(layoutListEmpty, View.GONE); + DatabaseHelper dbHelper = DatabaseHelper.getInstance(); + if (dbHelper != null) { + FetchViewHistoryTask task = new FetchViewHistoryTask(lastDate, PAGE_SIZE, dbHelper, new FetchViewHistoryTask.FetchViewHistoryHandler() { + @Override + public void onSuccess(List history, boolean hasReachedEnd) { + listReachedEnd = hasReachedEnd; + if (history.size() > 0) { + lastDate = history.get(history.size() - 1).getTimestamp(); + } + + List claims = Helper.claimsFromViewHistory(history); + if (contentListAdapter == null) { + initContentListAdapter(claims); + } else { + contentListAdapter.addItems(claims); + } + if (contentList.getAdapter() == null) { + contentList.setAdapter(contentListAdapter); + } + checkListEmpty(); + contentListLoading = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + checkListEmpty(); + contentListLoading = false; + } + } + + public void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) { + if ("abort".equals(downloadAction)) { + if (contentListAdapter != null) { + contentListAdapter.clearFileForClaimOrUrl(outpoint, uri, currentFilter == FILTER_DOWNLOADS); + } + return; + } + + try { + JSONObject fileInfo = new JSONObject(fileInfoJson); + LbryFile claimFile = LbryFile.fromJSONObject(fileInfo); + String claimId = claimFile.getClaimId(); + if (contentListAdapter != null) { + contentListAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri, true); + } + } catch (JSONException ex) { + // invalid file info for download + } + } + + private void checkListEmpty() { + layoutListEmpty.setVisibility(contentListAdapter == null || contentListAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + int stringResourceId; + switch (currentFilter) { + case FILTER_DOWNLOADS: default: stringResourceId = R.string.library_no_downloads; break; + case FILTER_HISTORY: stringResourceId = R.string.library_no_history; break; + case FILTER_PURCHASES: stringResourceId = R.string.library_no_purchases; break; + } + textListEmpty.setText(stringResourceId); + } + + private void addFiles(List files) { + if (currentFiles == null) { + currentFiles = new ArrayList<>(); + } + for (LbryFile file : files) { + if (!currentFiles.contains(file)) { + currentFiles.add(file); + } + } + } + + private void updateStats() { + totalBytes = 0; + totalVideoBytes = 0; + totalAudioBytes = 0; + totalImageBytes = 0; + totalOtherBytes = 0; + if (currentFiles != null) { + for (LbryFile file : currentFiles) { + long writtenBytes = file.getWrittenBytes(); + String mime = file.getMimeType(); + if (mime != null) { + if (mime.startsWith("video/")) { + totalVideoBytes += writtenBytes; + } else if (mime.startsWith("audio/")) { + totalAudioBytes += writtenBytes; + } else if (mime.startsWith("image/")) { + totalImageBytes += writtenBytes; + } else { + totalOtherBytes += writtenBytes; + } + } + + totalBytes += writtenBytes; + } + } + + renderStats(); + } + + private void renderStats() { + String[] totalSizeParts = Helper.formatBytesParts(totalBytes, false); + textStatsTotalSize.setText(totalSizeParts[0]); + textStatsTotalSizeUnits.setText(totalSizeParts[1]); + + viewStatsDistribution.setVisibility(totalBytes > 0 ? View.VISIBLE : View.GONE); + + int percentVideo = normalizePercent((double) totalVideoBytes / (double) totalBytes * 100.0); + legendVideo.setVisibility(totalVideoBytes > 0 ? View.VISIBLE : View.GONE); + textStatsVideoSize.setText(Helper.formatBytes(totalVideoBytes, false)); + applyLayoutWeight(viewVideoStatsBar, percentVideo); + + int percentAudio = normalizePercent((double) totalAudioBytes / (double) totalBytes * 100.0); + legendAudio.setVisibility(totalAudioBytes > 0 ? View.VISIBLE : View.GONE); + textStatsAudioSize.setText(Helper.formatBytes(totalAudioBytes, false)); + applyLayoutWeight(viewAudioStatsBar, percentAudio); + + int percentImage = normalizePercent((double) totalImageBytes / (double) totalBytes * 100.0); + legendImage.setVisibility(totalImageBytes > 0 ? View.VISIBLE : View.GONE); + textStatsImageSize.setText(Helper.formatBytes(totalImageBytes, false)); + applyLayoutWeight(viewImageStatsBar, percentImage); + + int percentOther = normalizePercent((double) totalOtherBytes / (double) totalBytes * 100.0); + legendOther.setVisibility(totalOtherBytes > 0 ? View.VISIBLE : View.GONE); + textStatsOtherSize.setText(Helper.formatBytes(totalOtherBytes, false)); + applyLayoutWeight(viewOtherStatsBar, percentOther); + + // We have to get to 100 (or adjust the container accordingly) + int totalPercent = percentVideo + percentAudio + percentImage + percentOther; + ((LinearLayout) viewStatsDistribution).setWeightSum(totalPercent); + } + + private void applyLayoutWeight(View view, int weight) { + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams(); + params.weight = weight; + } + + private static int normalizePercent(double value) { + if (value > 0 && value < 1) { + return 1; + } + return Double.valueOf(Math.floor(value)).intValue(); + } + + private void checkStatsLink() { + linkStats.setVisibility(cardStats.getVisibility() == View.VISIBLE || + listLoading.getVisibility() == View.VISIBLE || + currentFilter != FILTER_DOWNLOADS || + !Lbry.SDK_READY ? + View.GONE : View.VISIBLE); + } + + @Override + public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { + this.actionMode = actionMode; + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (!activity.isDarkMode()) { + activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + } + } + + actionMode.getMenuInflater().inflate(R.menu.menu_claim_list, menu); + return true; + } + @Override + public void onDestroyActionMode(ActionMode actionMode) { + if (contentListAdapter != null) { + contentListAdapter.clearSelectedItems(); + contentListAdapter.setInSelectionMode(false); + contentListAdapter.notifyDataSetChanged(); + } + Context context = getContext(); + if (context != null) { + MainActivity activity = (MainActivity) context; + if (!activity.isDarkMode()) { + activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + } + this.actionMode = null; + } + + @Override + public boolean onPrepareActionMode(androidx.appcompat.view.ActionMode actionMode, Menu menu) { + menu.findItem(R.id.action_edit).setVisible(false); + return true; + } + + @Override + public boolean onActionItemClicked(androidx.appcompat.view.ActionMode actionMode, MenuItem menuItem) { + if (R.id.action_delete == menuItem.getItemId()) { + if (contentListAdapter != null && contentListAdapter.getSelectedCount() > 0) { + final List selectedClaims = new ArrayList<>(contentListAdapter.getSelectedItems()); + String message = getResources().getQuantityString(R.plurals.confirm_delete_files, selectedClaims.size()); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()). + setTitle(R.string.delete_selection). + setMessage(message) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + handleDeleteSelectedClaims(selectedClaims); + } + }).setNegativeButton(R.string.no, null); + builder.show(); + return true; + } + } + + return false; + } + + private void handleDeleteSelectedClaims(List selectedClaims) { + List claimIds = new ArrayList<>(); + for (Claim claim : selectedClaims) { + claimIds.add(claim.getClaimId()); + } + + new BulkDeleteFilesTask(claimIds).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + Lbry.unsetFilesForCachedClaims(claimIds); + if (currentFilter == FILTER_DOWNLOADS) { + contentListAdapter.removeItems(selectedClaims); + } + if (actionMode != null) { + actionMode.finish(); + } + View root = getView(); + if (root != null) { + String message = getResources().getQuantityString(R.plurals.files_deleted, claimIds.size()); + Snackbar.make(root, message, Snackbar.LENGTH_LONG).show(); + } + } + + private List buildUrlsToResolve(List claims) { + List urls = new ArrayList<>(); + for (Claim claim : claims) { + Claim channel = claim.getSigningChannel(); + if (channel != null && Helper.isNullOrEmpty(channel.getName()) && !Helper.isNullOrEmpty(channel.getClaimId())) { + LbryUri uri = LbryUri.tryParse(String.format("%s#%s", claim.getName(), claim.getClaimId())); + if (uri != null) { + urls.add(uri.toString()); + } + } + } + return urls; + } + + private void resolveMissingChannelNames(List urls) { + if (urls.size() > 0) { + ResolveTask task = new ResolveTask(urls, Lbry.SDK_CONNECTION_STRING, null, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + boolean updated = false; + for (Claim claim : claims) { + if (claim.getClaimId() == null) { + continue; + } + + if (contentListAdapter != null) { + contentListAdapter.updateSigningChannelForClaim(claim); + updated = true; + } + } + if (updated) { + contentListAdapter.notifyDataSetChanged(); + } + } + + @Override + public void onError(Exception error) { + + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + public void onEnterSelectionMode() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.startSupportActionMode(this); + } + } + public void onItemSelectionToggled() { + if (actionMode != null) { + actionMode.setTitle(String.valueOf(contentListAdapter.getSelectedCount())); + actionMode.invalidate(); + } + } + public void onExitSelectionMode() { + if (actionMode != null) { + actionMode.finish(); + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/other/AboutFragment.java b/app/src/main/java/io/lbry/browser/ui/other/AboutFragment.java new file mode 100644 index 00000000..0923ad1b --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/other/AboutFragment.java @@ -0,0 +1,245 @@ +package io.lbry.browser.ui.other; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.appcompat.app.ActionBar; +import androidx.core.content.FileProvider; +import androidx.preference.PreferenceManager; + +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.android.material.snackbar.Snackbar; +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.InstanceIdResult; + +import org.json.JSONObject; + +import java.io.File; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.Lbryio; +import io.lbry.lbrysdk.Utils; + +public class AboutFragment extends BaseFragment implements SdkStatusListener { + + private static final String FILE_PROVIDER = "io.lbry.browser.fileprovider"; + + private TextView textLinkWhatIsLBRY; + private TextView textLinkAndroidBasics; + private TextView textLinkFAQ; + private TextView textLinkDiscord; + private TextView textLinkFacebook; + private TextView textLinkInstagram; + private TextView textLinkReddit; + private TextView textLinkTelegram; + private TextView textLinkTwitter; + + private TextView textConnectedEmail; + private TextView textAppVersion; + private TextView textLbrySdkVersion; + private TextView textPlatform; + private TextView textInstallationId; + private TextView textFirebaseToken; + private View linkSendLog; + private View linkUpdateMailingPreferences; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_about, container, false); + + textLinkWhatIsLBRY = root.findViewById(R.id.about_link_what_is_lbry); + textLinkAndroidBasics = root.findViewById(R.id.about_link_android_basics); + textLinkFAQ = root.findViewById(R.id.about_link_faq); + textLinkDiscord = root.findViewById(R.id.about_link_discord); + textLinkFacebook = root.findViewById(R.id.about_link_facebook); + textLinkInstagram = root.findViewById(R.id.about_link_instagram); + textLinkReddit = root.findViewById(R.id.about_link_reddit); + textLinkTelegram = root.findViewById(R.id.about_link_telegram); + textLinkTwitter = root.findViewById(R.id.about_link_twitter); + + TextView[] textLinks = { + textLinkWhatIsLBRY, textLinkAndroidBasics, textLinkFAQ, textLinkDiscord, textLinkFacebook, + textLinkInstagram, textLinkReddit, textLinkTelegram, textLinkTwitter + }; + for (TextView view : textLinks) { + Helper.applyHtmlForTextView(view); + } + + textConnectedEmail = root.findViewById(R.id.about_connected_email); + textAppVersion = root.findViewById(R.id.about_app_version); + textLbrySdkVersion = root.findViewById(R.id.about_lbry_sdk); + textPlatform = root.findViewById(R.id.about_platform); + textInstallationId = root.findViewById(R.id.about_installation_id); + textFirebaseToken = root.findViewById(R.id.about_firebase_token); + linkSendLog = root.findViewById(R.id.about_send_log); + linkUpdateMailingPreferences = root.findViewById(R.id.about_update_mailing_preferences); + + if (Lbryio.isSignedIn()) { + textConnectedEmail.setText(Lbryio.getSignedInEmail()); + textConnectedEmail.setTypeface(null, Typeface.NORMAL); + linkUpdateMailingPreferences.setVisibility(View.VISIBLE); + } else { + linkUpdateMailingPreferences.setVisibility(View.GONE); + } + + Context context = getContext(); + String appVersion = getString(R.string.unknown); + if (context != null) { + try { + PackageManager manager = context.getPackageManager(); + PackageInfo info = manager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES); + appVersion = info.versionName; + } catch (PackageManager.NameNotFoundException ex) { + // pass + } + } + textAppVersion.setText(appVersion); + textInstallationId.setText(Lbry.INSTALLATION_ID); + textPlatform.setText(String.format("Android %s (API %d)", Utils.getAndroidRelease(), Utils.getAndroidSdk())); + + linkUpdateMailingPreferences.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("http://lbry.com/list/edit/%s", Lbryio.AUTH_TOKEN))); + startActivity(intent); + } + }); + + linkSendLog.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + shareLogFile(); + } + }); + + return root; + } + + private void shareLogFile() { + Context context = getContext(); + if (context != null) { + String logFileName = "lbrynet.log"; + File logFile = new File(String.format("%s/%s", Utils.getAppInternalStorageDir(context), "lbrynet"), logFileName); + if (!logFile.exists()) { + Snackbar.make(getView(), R.string.cannot_find_lbrynet_log, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED). + setTextColor(Color.WHITE). + show(); + return; + } + + try { + Uri fileUri = FileProvider.getUriForFile(getContext(), FILE_PROVIDER, logFile); + if (fileUri != null) { + MainActivity.startingShareActivity = true; + Intent shareIntent = new Intent(); + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.setType("text/plain"); + shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri); + + Intent sendLogIntent = Intent.createChooser(shareIntent, "Send LBRY log"); + sendLogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(sendLogIntent); + } + } catch (IllegalArgumentException e) { + Snackbar.make(getView(), R.string.cannot_share_lbrynet_log, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED). + setTextColor(Color.WHITE). + show(); + } + } + } + + @Override + public void onStart() { + super.onStart(); + MainActivity activity = (MainActivity) getContext(); + if (activity != null) { + activity.hideSearchBar(); + activity.showNavigationBackIcon(); + activity.lockDrawer(); + activity.hideFloatingWalletBalance(); + + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(R.string.about_lbry); + } + } + } + + @Override + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "About", "About"); + + if (!Lbry.SDK_READY) { + activity.addSdkStatusListener(this); + } else { + onSdkReady(); + } + } + FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(Task task) { + Helper.setViewText(textFirebaseToken, task.isSuccessful() ? task.getResult().getToken() : getString(R.string.unknown)); + } + }); + + } + + @Override + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) getContext(); + activity.removeSdkStatusListener(this); + activity.restoreToggle(); + activity.showFloatingWalletBalance(); + } + super.onStop(); + } + + public void onSdkReady() { + loadLbryVersion(); + } + + private void loadLbryVersion() { + (new AsyncTask() { + protected String doInBackground(Void... params) { + try { + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_VERSION); + return Helper.getJSONString("lbrynet_version", null, result); + } catch (ApiCallException | ClassCastException ex) { + // pass + return null; + } + } + protected void onPostExecute(String version) { + Helper.setViewText(textLbrySdkVersion, Helper.isNullOrEmpty(version) ? getString(R.string.unknown) : version); + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/other/SettingsFragment.java b/app/src/main/java/io/lbry/browser/ui/other/SettingsFragment.java new file mode 100644 index 00000000..6cf4deaf --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/other/SettingsFragment.java @@ -0,0 +1,103 @@ +package io.lbry.browser.ui.other; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; + +import java.io.FileOutputStream; +import java.io.PrintStream; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.lbrysdk.Utils; + +public class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + setPreferencesFromResource(R.xml.settings, rootKey); + } + + @Override + public void onStart() { + super.onStart(); + MainActivity activity = (MainActivity) getContext(); + if (activity != null) { + activity.hideSearchBar(); + activity.showNavigationBackIcon(); + activity.lockDrawer(); + activity.hideFloatingWalletBalance(); + + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(R.string.settings); + } + } + } + + @Override + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context instanceof MainActivity) { + PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this); + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Settings", "Settings"); + } + } + @Override + public void onPause() { + Context context = getContext(); + if (context != null) { + PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this); + } + super.onPause(); + } + @Override + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) getContext(); + activity.restoreToggle(); + activity.showFloatingWalletBalance(); + } + super.onStop(); + } + + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (key.equalsIgnoreCase(MainActivity.PREFERENCE_KEY_DARK_MODE)) { + boolean darkMode = sp.getBoolean(MainActivity.PREFERENCE_KEY_DARK_MODE, false); + AppCompatDelegate.setDefaultNightMode(darkMode ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); + } else if (key.equalsIgnoreCase(MainActivity.PREFERENCE_KEY_PARTICIPATE_DATA_NETWORK)) { + boolean dhtEnabled = sp.getBoolean(MainActivity.PREFERENCE_KEY_PARTICIPATE_DATA_NETWORK, false); + updateDHTFileSetting(dhtEnabled); + } + } + + private void updateDHTFileSetting(final boolean enabled) { + Context context = getContext(); + (new AsyncTask() { + protected Void doInBackground(Void... params) { + PrintStream out = null; + try { + String fileContent = enabled ? "on" : "off"; + String path = String.format("%s/%s", Utils.getAppInternalStorageDir(context), "dht"); + out = new PrintStream(new FileOutputStream(path)); + out.print(fileContent); + } catch (Exception ex) { + // pass + } finally { + Helper.closeCloseable(out); + } + return null; + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/publish/PublishFormFragment.java b/app/src/main/java/io/lbry/browser/ui/publish/PublishFormFragment.java new file mode 100644 index 00000000..7e5614ac --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/publish/PublishFormFragment.java @@ -0,0 +1,1644 @@ +package io.lbry.browser.ui.publish; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.media.ThumbnailUtils; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.provider.MediaStore; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.MimeTypeMap; +import android.widget.AdapterView; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.AppCompatSpinner; +import androidx.cardview.widget.CardView; +import androidx.core.content.ContextCompat; +import androidx.core.widget.NestedScrollView; +import androidx.recyclerview.widget.RecyclerView; + +import com.arthenica.mobileffmpeg.Config; +import com.arthenica.mobileffmpeg.FFmpeg; +import com.arthenica.mobileffmpeg.FFprobe; + +import com.arthenica.mobileffmpeg.Statistics; +import com.arthenica.mobileffmpeg.StatisticsCallback; +import com.bumptech.glide.Glide; +import com.google.android.flexbox.FlexboxLayoutManager; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.switchmaterial.SwitchMaterial; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileOutputStream; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.BuildConfig; +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.InlineChannelSpinnerAdapter; +import io.lbry.browser.adapter.LanguageSpinnerAdapter; +import io.lbry.browser.adapter.LicenseSpinnerAdapter; +import io.lbry.browser.adapter.TagListAdapter; +import io.lbry.browser.listener.FilePickerListener; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.listener.StoragePermissionListener; +import io.lbry.browser.listener.WalletBalanceListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.Fee; +import io.lbry.browser.model.GalleryItem; +import io.lbry.browser.model.Language; +import io.lbry.browser.model.License; +import io.lbry.browser.model.NavMenuItem; +import io.lbry.browser.model.Tag; +import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.tasks.claim.ChannelCreateUpdateTask; +import io.lbry.browser.tasks.UpdateSuggestedTagsTask; +import io.lbry.browser.tasks.UploadImageTask; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimListTask; +import io.lbry.browser.tasks.claim.ClaimResultHandler; +import io.lbry.browser.tasks.claim.PublishClaimTask; +import io.lbry.browser.tasks.lbryinc.LogPublishTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Predefined; +import io.lbry.lbrysdk.Utils; +import lombok.Data; +import lombok.Getter; + +public class PublishFormFragment extends BaseFragment implements + FilePickerListener, SdkStatusListener, StoragePermissionListener, TagListAdapter.TagClickListener, WalletBalanceListener { + + private static final String H264_CODEC = "h264"; + private static final int MAX_VIDEO_DIMENSION = 1920; + private static final int MAX_BITRATE = 5000000; // 5mbps + + private static final int SUGGESTED_LIMIT = 8; + + private boolean editMode; + @Getter + private boolean saveInProgress; + private String currentFilter; + private boolean publishFileChecked; + private boolean fetchingChannels; + private boolean launchPickerPending; + @Getter + private boolean transcodeInProgress; + private long transcodeStartTime; + private VideoTranscodeTask videoTranscodeTask; + + private TextInputEditText inputTagFilter; + private RecyclerView addedTagsList; + private RecyclerView suggestedTagsList; + private RecyclerView matureTagsList; + private TagListAdapter addedTagsAdapter; + private TagListAdapter suggestedTagsAdapter; + private TagListAdapter matureTagsAdapter; + private ProgressBar progressPublish; + private ProgressBar progressLoadingChannels; + private View noTagsView; + private View noTagResultsView; + + private InlineChannelSpinnerAdapter channelSpinnerAdapter; + private AppCompatSpinner channelSpinner; + private AppCompatSpinner priceCurrencySpinner; + private AppCompatSpinner languageSpinner; + private AppCompatSpinner licenseSpinner; + + private NestedScrollView scrollView; + private View layoutExtraFields; + private TextView linkShowExtraFields; + private View textNoPrice; + private View layoutPrice; + private SwitchMaterial switchPrice; + private ImageView imageThumbnail; + private TextView linkGenerateAddress; + + private TextInputEditText inputTitle; + private TextInputEditText inputDescription; + private TextInputEditText inputPrice; + private TextInputEditText inputAddress; + private TextInputEditText inputDeposit; + private TextInputEditText inputOtherLicenseDescription; + private TextInputLayout layoutOtherLicenseDescription; + private View inlineDepositBalanceContainer; + private TextView inlineDepositBalanceValue; + private TextView textInlineAddressInvalid; + + private View linkPublishCancel; + private MaterialButton buttonPublish; + + private View inlineChannelCreator; + private TextInputEditText inlineChannelCreatorInputName; + private TextInputEditText inlineChannelCreatorInputDeposit; + private View inlineChannelCreatorInlineBalance; + private TextView inlineChannelCreatorInlineBalanceValue; + private View inlineChannelCreatorCancelLink; + private View inlineChannelCreatorProgress; + private MaterialButton inlineChannelCreatorCreateButton; + + private boolean uploading; + private String lastSelectedThumbnailFile; + private String uploadedThumbnailUrl; + private boolean editFieldsLoaded; + private boolean editChannelSpinnerLoaded; + private Claim currentClaim; + private GalleryItem currentGalleryItem; + private String currentFilePath; + private String transcodedFilePath; + + private View mediaContainer; + private View uploadProgress; + private CardView cardVideoOptimization; + private ProgressBar optimizationRealProgress; + private ProgressBar optimizationProgress; + private TextView textOptimizationProgress; + private TextView textOptimizationStatus; + private TextView textOptimizationElapsed; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_publish_form, container, false); + + scrollView = root.findViewById(R.id.publish_form_scroll_view); + progressLoadingChannels = root.findViewById(R.id.publish_form_loading_channels); + progressPublish = root.findViewById(R.id.publish_form_publishing); + channelSpinner = root.findViewById(R.id.publish_form_channel_spinner); + mediaContainer = root.findViewById(R.id.publish_form_media_container); + + inputTagFilter = root.findViewById(R.id.form_tag_filter_input); + noTagsView = root.findViewById(R.id.form_no_added_tags); + noTagResultsView = root.findViewById(R.id.form_no_tag_results); + + textInlineAddressInvalid = root.findViewById(R.id.publish_form_inline_address_invalid); + inlineDepositBalanceContainer = root.findViewById(R.id.publish_form_inline_balance_container); + inlineDepositBalanceValue = root.findViewById(R.id.publish_form_inline_balance_value); + + cardVideoOptimization = root.findViewById(R.id.publish_form_video_opt_card); + optimizationProgress = root.findViewById(R.id.publish_form_video_opt_progress); + optimizationRealProgress = root.findViewById(R.id.publish_form_video_opt_real_progress); + textOptimizationProgress = root.findViewById(R.id.publish_form_video_opt_progress_text); + textOptimizationStatus = root.findViewById(R.id.publish_form_video_opt_status); + textOptimizationElapsed = root.findViewById(R.id.publish_form_video_opt_elapsed); + + layoutExtraFields = root.findViewById(R.id.publish_form_extra_options_container); + linkShowExtraFields = root.findViewById(R.id.publish_form_toggle_extra); + layoutPrice = root.findViewById(R.id.publish_form_price_container); + textNoPrice = root.findViewById(R.id.publish_form_no_price); + switchPrice = root.findViewById(R.id.publish_form_price_switch); + uploadProgress = root.findViewById(R.id.publish_form_thumbnail_upload_progress); + imageThumbnail = root.findViewById(R.id.publish_form_thumbnail_preview); + linkGenerateAddress = root.findViewById(R.id.publish_form_generate_address); + + inputTitle = root.findViewById(R.id.publish_form_input_title); + inputDescription = root.findViewById(R.id.publish_form_input_description); + inputPrice = root.findViewById(R.id.publish_form_input_price); + inputAddress = root.findViewById(R.id.publish_form_input_address); + inputDeposit = root.findViewById(R.id.publish_form_input_deposit); + inputOtherLicenseDescription = root.findViewById(R.id.publish_form_input_license_other); + layoutOtherLicenseDescription = root.findViewById(R.id.publish_form_license_other_layout); + priceCurrencySpinner = root.findViewById(R.id.publish_form_currency_spinner); + languageSpinner = root.findViewById(R.id.publish_form_language_spinner); + licenseSpinner = root.findViewById(R.id.publish_form_license_spinner); + + linkPublishCancel = root.findViewById(R.id.publish_form_cancel); + buttonPublish = root.findViewById(R.id.publish_form_publish_button); + + Context context = getContext(); + FlexboxLayoutManager flm1 = new FlexboxLayoutManager(context); + FlexboxLayoutManager flm2 = new FlexboxLayoutManager(context); + FlexboxLayoutManager flm3 = new FlexboxLayoutManager(context); + addedTagsList = root.findViewById(R.id.form_added_tags); + addedTagsList.setLayoutManager(flm1); + suggestedTagsList = root.findViewById(R.id.form_suggested_tags); + suggestedTagsList.setLayoutManager(flm2); + + root.findViewById(R.id.form_mature_tags_container).setVisibility(View.VISIBLE); + matureTagsList = root.findViewById(R.id.form_mature_tags); + matureTagsList.setLayoutManager(flm3); + + addedTagsAdapter = new TagListAdapter(new ArrayList<>(), context); + addedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_REMOVE); + addedTagsAdapter.setClickListener(this); + addedTagsList.setAdapter(addedTagsAdapter); + + suggestedTagsAdapter = new TagListAdapter(new ArrayList<>(), getContext()); + suggestedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD); + suggestedTagsAdapter.setClickListener(this); + suggestedTagsList.setAdapter(suggestedTagsAdapter); + + matureTagsAdapter = new TagListAdapter(Helper.getTagObjectsForTags(Predefined.MATURE_TAGS), context); + matureTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD); + matureTagsAdapter.setClickListener(this); + matureTagsList.setAdapter(matureTagsAdapter); + + inlineChannelCreator = root.findViewById(R.id.container_inline_channel_form_create); + inlineChannelCreatorInputName = root.findViewById(R.id.inline_channel_form_input_name); + inlineChannelCreatorInputDeposit = root.findViewById(R.id.inline_channel_form_input_deposit); + inlineChannelCreatorInlineBalance = root.findViewById(R.id.inline_channel_form_inline_balance_container); + inlineChannelCreatorInlineBalanceValue = root.findViewById(R.id.inline_channel_form_inline_balance_value); + inlineChannelCreatorProgress = root.findViewById(R.id.inline_channel_form_create_progress); + inlineChannelCreatorCancelLink = root.findViewById(R.id.inline_channel_form_cancel_link); + inlineChannelCreatorCreateButton = root.findViewById(R.id.inline_channel_form_create_button); + + initUi(); + + return root; + } + + private void initUi() { + Context context = getContext(); + languageSpinner.setAdapter(new LanguageSpinnerAdapter(context, R.layout.spinner_item_generic)); + licenseSpinner.setAdapter(new LicenseSpinnerAdapter(context, R.layout.spinner_item_generic)); + + licenseSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, long l) { + License license = (License) adapterView.getAdapter().getItem(position); + boolean otherLicense = Arrays.asList( + Predefined.LICENSE_COPYRIGHTED.toLowerCase(), + Predefined.LICENSE_OTHER.toLowerCase()).contains(license.getName().toLowerCase()); + Helper.setViewVisibility(layoutOtherLicenseDescription, otherLicense ? View.VISIBLE : View.GONE); + if (!otherLicense) { + inputOtherLicenseDescription.setText(null); + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + linkGenerateAddress.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!editMode) { + inputAddress.setText(Helper.generateUrl()); + } + } + }); + + switchPrice.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + Helper.setViewVisibility(textNoPrice, checked ? View.GONE : View.VISIBLE); + Helper.setViewVisibility(layoutPrice, checked ? View.VISIBLE : View.GONE); + } + }); + + inputAddress.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + String value = Helper.getValue(charSequence); + boolean invalid = !Helper.isNullOrEmpty(value) && !LbryUri.isNameValid(value); + Helper.setViewVisibility(textInlineAddressInvalid, invalid ? View.VISIBLE : View.INVISIBLE); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + + inputDeposit.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + Helper.setViewVisibility(inlineDepositBalanceContainer, hasFocus ? View.VISIBLE : View.GONE); + } + }); + + linkShowExtraFields.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (layoutExtraFields.getVisibility() != View.VISIBLE) { + layoutExtraFields.setVisibility(View.VISIBLE); + linkShowExtraFields.setText(R.string.hide_extra_fields); + scrollView.post(new Runnable() { + @Override + public void run() { + scrollView.fullScroll(NestedScrollView.FOCUS_DOWN); + } + }); + } else { + layoutExtraFields.setVisibility(View.GONE); + linkShowExtraFields.setText(R.string.show_extra_fields); + } + } + }); + + mediaContainer.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + checkStoragePermissionAndLaunchFilePicker(); + } + }); + + channelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, long l) { + Object item = adapterView.getItemAtPosition(position); + if (item instanceof Claim) { + Claim claim = (Claim) item; + if (claim.isPlaceholder() && !claim.isPlaceholderAnonymous()) { + if (!fetchingChannels) { + showInlineChannelCreator(); + } + } else { + hideInlineChannelCreator(); + } + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + inputTagFilter.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + String value = Helper.getValue(charSequence); + setFilter(value); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + + linkPublishCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (transcodeInProgress) { + // show alert confirming the user is sure, and then cancel + FFmpeg.cancel(); + transcodeInProgress = false; + } + + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).onBackPressed(); + } + } + }); + + buttonPublish.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (uploading) { + Snackbar.make(view, R.string.publish_no_thumbnail, Snackbar.LENGTH_LONG).show(); + return; + } else if (Helper.isNullOrEmpty(uploadedThumbnailUrl)) { + showError(getString(R.string.publish_thumbnail_in_progress)); + return; + } + + if (transcodeInProgress) { + Snackbar.make(view, R.string.optimization_in_progress, Snackbar.LENGTH_LONG).show(); + return; + } + + // check minimum deposit + String depositString = Helper.getValue(inputDeposit.getText()); + double depositAmount = 0; + try { + depositAmount = Double.valueOf(depositString); + } catch (NumberFormatException ex) { + // pass + showError(getString(R.string.please_enter_valid_deposit)); + return; + } + if (depositAmount < Helper.MIN_DEPOSIT) { + String error = getResources().getQuantityString(R.plurals.min_deposit_required, depositAmount == 1 ? 1 : 2, String.valueOf(Helper.MIN_DEPOSIT)); + showError(error); + return; + } + if (Lbry.walletBalance == null || Lbry.walletBalance.getAvailable().doubleValue() < depositAmount) { + showError(getString(R.string.deposit_more_than_balance)); + return; + } + + String priceString = Helper.getValue(inputPrice.getText()); + double priceAmount = Helper.parseDouble(priceString, 0); + if (switchPrice.isChecked() && priceAmount == 0) { + showError(getString(R.string.price_amount_not_set)); + return; + } + + Claim claim = buildPublishClaim(); + if (validatePublishClaim(claim)) { + publishClaim(claim); + } + } + }); + + setupInlineChannelCreator( + inlineChannelCreator, + inlineChannelCreatorInputName, + inlineChannelCreatorInputDeposit, + inlineChannelCreatorInlineBalance, + inlineChannelCreatorInlineBalanceValue, + inlineChannelCreatorCancelLink, + inlineChannelCreatorCreateButton, + inlineChannelCreatorProgress + ); + } + + @Override + public void onStart() { + super.onStart(); + MainActivity activity = (MainActivity) getContext(); + if (activity != null) { + activity.hideSearchBar(); + activity.showNavigationBackIcon(); + activity.lockDrawer(); + activity.hideFloatingWalletBalance(); + + activity.addFilePickerListener(this); + activity.addWalletBalanceListener(this); + + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(editMode ? R.string.edit_content : R.string.new_publish); + } + } + } + + @Override + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) getContext(); + activity.restoreToggle(); + activity.showFloatingWalletBalance(); + if (!MainActivity.startingFilePickerActivity) { + activity.removeWalletBalanceListener(this); + activity.removeFilePickerListener(this); + activity.removeNavFragment(PublishFormFragment.class, NavMenuItem.ID_ITEM_NEW_PUBLISH); + if (transcodeInProgress) { + FFmpeg.cancel(); + } + } + } + + super.onStop(); + } + + private void checkParams() { + Map params = getParams(); + if (params != null) { + if (params.containsKey("claim")) { + Claim claim = (Claim) params.get("claim"); + if (claim != null && !claim.equals(this.currentClaim)) { + this.currentClaim = claim; + editFieldsLoaded = false; + } + } else if (params.containsKey("galleryItem")) { + currentGalleryItem = (GalleryItem) params.get("galleryItem"); + } else if (params.containsKey("directFilePath")) { + currentFilePath = (String) params.get("directFilePath"); + } + + if (this.currentClaim == null && params.containsKey("suggestedUrl")) { + String suggestedUrl = (String) params.get("suggestedUrl"); + if (!Helper.isNullOrEmpty(suggestedUrl) && Helper.isNullOrEmpty(Helper.getValue(inputAddress.getText()))) { + Helper.setViewText(inputAddress, (String) params.get("suggestedUrl")); + } + } + } else { + // shouldn't actually happen + cancelOnFatalCondition(getString(R.string.no_file_found)); + } + } + + private void checkStoragePermissionAndLaunchFilePicker() { + Context context = getContext(); + if (MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context)) { + launchPickerPending = false; + launchFilePicker(); + } else { + launchPickerPending = true; + MainActivity.requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, + MainActivity.REQUEST_STORAGE_PERMISSION, + getString(R.string.storage_permission_rationale_images), + context, + true); + } + } + + private void launchFilePicker() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity.startingFilePickerActivity = true; + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("image/*"); + ((MainActivity) context).startActivityForResult( + Intent.createChooser(intent, getString(R.string.select_thumbnail)), + MainActivity.REQUEST_FILE_PICKER); + } + } + + private void updateFieldsFromCurrentClaim() { + if (currentClaim != null && !editFieldsLoaded) { + Context context = getContext(); + Claim.StreamMetadata metadata = (Claim.StreamMetadata) currentClaim.getValue(); + uploadedThumbnailUrl = currentClaim.getThumbnailUrl(); + if (context != null && !Helper.isNullOrEmpty(uploadedThumbnailUrl)) { + Glide.with(context.getApplicationContext()).load(uploadedThumbnailUrl).centerCrop().into(imageThumbnail); + } + + inputTitle.setText(currentClaim.getTitle()); + inputDescription.setText(currentClaim.getDescription()); + if (addedTagsAdapter != null && currentClaim.getTagObjects() != null) { + addedTagsAdapter.addTags(currentClaim.getTagObjects()); + updateSuggestedTags(currentFilter, SUGGESTED_LIMIT, true); + } + + if (metadata.getFee() != null) { + Fee fee = metadata.getFee(); + switchPrice.setChecked(true); + inputPrice.setText(fee.getAmount()); + priceCurrencySpinner.setSelection("lbc".equalsIgnoreCase(fee.getCurrency()) ? 0 : 1); + } + + inputAddress.setText(currentClaim.getName()); + inputDeposit.setText(currentClaim.getAmount()); + + if (metadata.getLanguages() != null && metadata.getLanguages().size() > 0) { + // get the first language + String langCode = metadata.getLanguages().get(0); + int langCodePosition = ((LanguageSpinnerAdapter) languageSpinner.getAdapter()).getItemPosition(langCode); + if (langCodePosition > -1) { + languageSpinner.setSelection(langCodePosition); + } + } + + if (!Helper.isNullOrEmpty(metadata.getLicense())) { + LicenseSpinnerAdapter adapter = (LicenseSpinnerAdapter) licenseSpinner.getAdapter(); + int licPosition = adapter.getItemPosition(metadata.getLicense()); + if (licPosition == -1) { + licPosition = adapter.getItemPosition(Predefined.LICENSE_OTHER); + } + if (licPosition > -1) { + licenseSpinner.setSelection(licPosition); + } + + License selectedLicense = (License) licenseSpinner.getSelectedItem(); + boolean otherLicense = Arrays.asList( + Predefined.LICENSE_COPYRIGHTED.toLowerCase(), + Predefined.LICENSE_OTHER.toLowerCase()).contains(selectedLicense.getName().toLowerCase()); + inputOtherLicenseDescription.setText(otherLicense ? metadata.getLicense() : null); + } + + inputAddress.setEnabled(false); + editMode = true; + editFieldsLoaded = true; + } + } + + private void checkPublishFile() { + if (publishFileChecked) { + return; + } + + String filePath = ""; + String thumbnailPath = null; + if (currentGalleryItem != null) { + // check gallery item type + filePath = currentGalleryItem.getFilePath(); + thumbnailPath = currentGalleryItem.getThumbnailPath(); + } else if (currentFilePath != null) { + filePath = currentFilePath; + } + + File file = new File(filePath); + if (!file.exists()) { + // file doesn't exist. although this shouldn't happen + cancelOnFatalCondition(getString(R.string.no_file_found)); + return; + } + + // check content type + String type = null; + String extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()); + if (extension != null) { + type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + } + + boolean isVideo = false; + boolean isImage = !Helper.isNullOrEmpty(type) && type.startsWith("image"); + if (!Helper.isNullOrEmpty(type) && type.startsWith("video")) { + // ffmpeg video handling + isVideo = true; + if (!transcodeInProgress) { + probeVideo(filePath); + } + } + + if (isVideo || isImage) { + checkAndUploadThumbnail(filePath, thumbnailPath, isVideo ? "video" : "image"); + } + + Helper.setViewVisibility(cardVideoOptimization, isVideo ? View.VISIBLE : View.GONE); + + publishFileChecked = true; + } + + private void checkAndUploadThumbnail(String filePath, String thumbnailPath, String type) { + boolean thumbnailValid = false; + if (!Helper.isNullOrEmpty(thumbnailPath)) { + File file = new File(thumbnailPath); + // make sure the file exists and it's not an empty file + thumbnailValid = file.exists() && file.length() > 0; + } + + if (!thumbnailValid) { + createAndUploadThumbnail(filePath, type); + } else { + uploadThumbnail(thumbnailPath); + } + } + + private void createAndUploadThumbnail(String filePath, String type) { + Context context = getContext(); + CreateThumbnailTask task = new CreateThumbnailTask(filePath, type, context, new CreateThumbnailTask.CreateThumbnailHandler() { + @Override + public void onSuccess(String thumbnailPath) { + uploadThumbnail(thumbnailPath); + } + + @Override + public void onError(Exception error) { + if (context != null) { + showError(getString(R.string.thumbnail_creation_failed)); + } + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void uploadThumbnail(String thumbnailPath) { + if (uploading) { + Snackbar.make(getView(), R.string.wait_for_upload, Snackbar.LENGTH_LONG).show(); + return; + } + + Context context = getContext(); + if (context != null) { + Glide.with(context.getApplicationContext()).load(thumbnailPath).centerCrop().into(imageThumbnail); + } + + uploading = true; + uploadedThumbnailUrl = null; + UploadImageTask task = new UploadImageTask(thumbnailPath, uploadProgress, new UploadImageTask.UploadThumbnailHandler() { + @Override + public void onSuccess(String url) { + lastSelectedThumbnailFile = thumbnailPath; + uploadedThumbnailUrl = url; + uploading = false; + } + + @Override + public void onError(Exception error) { + View view = getView(); + if (context != null && view != null) { + showError(getString(R.string.image_upload_failed)); + } + lastSelectedThumbnailFile = null; + imageThumbnail.setImageDrawable(null); + uploading = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void probeVideo(String filePath) { + VideoProbeTask task = new VideoProbeTask(filePath, new VideoProbeTask.VideoProbeHandler() { + @Override + public void onVideoProbed(VideoInformation result) { + checkAndTranscodeVideo(filePath, result); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + + } + + private void checkAndTranscodeVideo(String filePath, VideoInformation videoInformation) { + boolean transcodeRequired = (videoInformation == null || + !H264_CODEC.equalsIgnoreCase(videoInformation.getCodecName()) || + MAX_VIDEO_DIMENSION < videoInformation.getWidth() || MAX_VIDEO_DIMENSION < videoInformation.getHeight() || + MAX_BITRATE < videoInformation.getBitrate()); + + String scalePart = ""; + if (videoInformation != null) { + // check the max dimension that we need to scale + int videoWidth = videoInformation.getWidth(); + int videoHeight = videoInformation.getHeight(); + // get the highest dimension + int maxDimension = Math.max(videoWidth, videoHeight); + if (maxDimension > MAX_VIDEO_DIMENSION) { + scalePart = maxDimension == videoWidth ? String.format("-vf scale=%d:-2", MAX_VIDEO_DIMENSION) : String.format("-vf scale=-2:%d", MAX_VIDEO_DIMENSION); + } + } + + Context context = getContext(); + String outputPath = String.format("%s/videos", Utils.getAppInternalStorageDir(context)); + File dir = new File(outputPath); + if (!dir.isDirectory()) { + dir.mkdirs(); + } + + boolean hasFullDuration = videoInformation != null && videoInformation.getDurationSeconds() > 0; + Helper.setViewVisibility(optimizationRealProgress, hasFullDuration ? View.VISIBLE : View.GONE); + Helper.setViewVisibility(optimizationProgress, hasFullDuration ? View.GONE : View.VISIBLE); + + File sourceFile = new File(filePath); + String filename = sourceFile.getName(); + if (!filename.endsWith(".mp4")) { + int lastDotIndex = filename.lastIndexOf('.'); + filename = String.format("%s.mp4", lastDotIndex > -1 ? filename.substring(0, lastDotIndex) : filename); + } + + String videoFilePath = String.format("%s/%s", outputPath, filename); + File targetFile = new File(videoFilePath); + if (targetFile.exists()) { + targetFile.delete(); + } + + transcodeInProgress = true; + videoTranscodeTask = new VideoTranscodeTask(filePath, videoFilePath, scalePart, transcodeRequired, new VideoTranscodeTask.VideoTranscodeHandler() { + @Override + public void onProgress(int time) { + if (context != null) { + int currentDuration = Double.valueOf(time / 1000.0).intValue(); + int fullDuration = videoInformation != null ? videoInformation.getDurationSeconds() : 0; + long elapsed = System.currentTimeMillis() - transcodeStartTime; + String completedDurationText = Helper.formatDuration(currentDuration); + if (fullDuration > 0) { + completedDurationText = String.format("%s / %s", completedDurationText, Helper.formatDuration(fullDuration)); + int percentComplete = Double.valueOf(Math.ceil((double) currentDuration / (double) fullDuration * 100.0)).intValue(); + optimizationRealProgress.setProgress(percentComplete); + } + + + String text = context.getString(R.string.completed_video_duration, completedDurationText); + Helper.setViewText(textOptimizationProgress, text); + Helper.setViewText(textOptimizationElapsed, Helper.formatDuration(Double.valueOf(elapsed / 1000.0).longValue())); + } + } + + @Override + public void onSuccess(String outputFilePath) { + transcodedFilePath = outputFilePath; + transcodeInProgress = false; + Helper.setViewText(textOptimizationStatus, R.string.video_optimized); + Helper.setViewVisibility(optimizationRealProgress, View.GONE); + Helper.setViewVisibility(optimizationProgress, View.GONE); + Helper.setViewVisibility(textOptimizationProgress, View.GONE); + } + + @Override + public void onErrorOrCancelled() { + transcodeInProgress = false; + Helper.setViewText(textOptimizationStatus, R.string.video_optimize_failed); + Helper.setViewVisibility(optimizationRealProgress, View.GONE); + Helper.setViewVisibility(optimizationProgress, View.GONE); + Helper.setViewVisibility(textOptimizationProgress, View.GONE); + } + }); + + transcodeStartTime = System.currentTimeMillis(); + videoTranscodeTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void cancelOnFatalCondition(String message) { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.showError(message); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + activity.onBackPressed(); + } + }, 100); + } + } + + public void onResume() { + super.onResume(); + if (!Lbry.SDK_READY) { + cancelOnFatalCondition(getString(R.string.sdk_initializing_functionality)); + return; + } + + checkParams(); + updateFieldsFromCurrentClaim(); + + if (currentClaim == null && (currentGalleryItem != null || !Helper.isNullOrEmpty(currentFilePath))) { + // load file information + checkPublishFile(); + } + + Context context = getContext(); + Helper.setWunderbarValue(null, context); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Channel Form", "ChannelForm"); + activity.addStoragePermissionListener(this); + if (editMode) { + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(R.string.edit_content); + } + } + } + + if (!Lbry.SDK_READY) { + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.addSdkStatusListener(this); + } + } else { + onSdkReady(); + } + + String filterText = Helper.getValue(inputTagFilter.getText()); + updateSuggestedTags(filterText, SUGGESTED_LIMIT, true); + } + + public void onSdkReady() { + fetchChannels(); + onWalletBalanceUpdated(Lbry.walletBalance); + } + + private void fetchChannels() { + if (Lbry.ownChannels != null && Lbry.ownChannels.size() > 0) { + updateChannelList(Lbry.ownChannels); + return; + } + + fetchingChannels = true; + disableChannelSpinner(); + ClaimListTask task = new ClaimListTask(Claim.TYPE_CHANNEL, progressLoadingChannels, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + Lbry.ownChannels = new ArrayList<>(claims); + updateChannelList(Lbry.ownChannels); + enableChannelSpinner(); + fetchingChannels = false; + } + + @Override + public void onError(Exception error) { + enableChannelSpinner(); + fetchingChannels = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + private void disableChannelSpinner() { + Helper.setViewEnabled(channelSpinner, false); + hideInlineChannelCreator(); + } + private void enableChannelSpinner() { + Helper.setViewEnabled(channelSpinner, true); + if (channelSpinner != null) { + Claim selectedClaim = (Claim) channelSpinner.getSelectedItem(); + if (selectedClaim != null) { + if (selectedClaim.isPlaceholder()) { + showInlineChannelCreator(); + } else { + hideInlineChannelCreator(); + } + } + } + } + private void showInlineChannelCreator() { + Helper.setViewVisibility(inlineChannelCreator, View.VISIBLE); + } + private void hideInlineChannelCreator() { + Helper.setViewVisibility(inlineChannelCreator, View.GONE); + } + + private void updateChannelList(List channels) { + if (channelSpinnerAdapter == null) { + Context context = getContext(); + if (context != null) { + channelSpinnerAdapter = new InlineChannelSpinnerAdapter(context, R.layout.spinner_item_channel, new ArrayList<>(channels)); + channelSpinnerAdapter.addPlaceholder(true); + channelSpinnerAdapter.notifyDataSetChanged(); + } + } else { + channelSpinnerAdapter.clear(); + channelSpinnerAdapter.addAll(channels); + channelSpinnerAdapter.addPlaceholder(true); + channelSpinnerAdapter.notifyDataSetChanged(); + } + + if (channelSpinner != null) { + channelSpinner.setAdapter(channelSpinnerAdapter); + } + + if (channelSpinnerAdapter != null && channelSpinner != null) { + if (editMode && currentClaim.getSigningChannel() != null && !editChannelSpinnerLoaded) { + int position = channelSpinnerAdapter.getItemPosition(currentClaim.getSigningChannel()); + if (position > -1) { + channelSpinner.setSelection(position); + } + editChannelSpinnerLoaded = true; + } else { + if (channelSpinnerAdapter.getCount() > 2) { + // if anonymous displayed, select first channel if available + channelSpinner.setSelection(2); + } else if (channelSpinnerAdapter.getCount() > 1) { + // select anonymous + channelSpinner.setSelection(1); + } + } + } + } + + private Claim buildPublishClaim() { + Claim claim = new Claim(); + + claim.setName(Helper.getValue(inputAddress.getText())); + claim.setAmount(Helper.getValue(inputDeposit.getText())); + + Claim.StreamMetadata metadata = new Claim.StreamMetadata(); + metadata.setTitle(Helper.getValue(inputTitle.getText())); + metadata.setDescription(Helper.getValue(inputDescription.getText())); + metadata.setTags(Helper.getTagsForTagObjects(addedTagsAdapter.getTags())); + + Claim selectedChannel = (Claim) channelSpinner.getSelectedItem(); + if (selectedChannel != null && !selectedChannel.isPlaceholder() && !selectedChannel.isPlaceholderAnonymous()) { + claim.setSigningChannel(selectedChannel); + } + if (switchPrice.isChecked()) { + Fee fee = new Fee(); + fee.setCurrency((String) priceCurrencySpinner.getSelectedItem()); + fee.setAmount(Helper.getValue(inputPrice.getText())); + metadata.setFee(fee); + } + if (!Helper.isNullOrEmpty(uploadedThumbnailUrl)) { + Claim.Resource thumbnail = new Claim.Resource(); + thumbnail.setUrl(uploadedThumbnailUrl); + metadata.setThumbnail(thumbnail); + } + + Language selectedLanguage = (Language) languageSpinner.getSelectedItem(); + if (selectedLanguage != null) { + metadata.setLanguages(Arrays.asList(selectedLanguage.getCode())); + } + + License selectedLicense = (License) licenseSpinner.getSelectedItem(); + if (selectedLicense != null) { + boolean otherLicense = Arrays.asList( + Predefined.LICENSE_COPYRIGHTED.toLowerCase(), + Predefined.LICENSE_OTHER.toLowerCase()).contains(selectedLicense.getName().toLowerCase()); + metadata.setLicense(otherLicense ? Helper.getValue(inputOtherLicenseDescription.getText()) : selectedLicense.getName()); + metadata.setLicenseUrl(selectedLicense.getUrl()); + } + + claim.setValueType(Claim.TYPE_STREAM); + claim.setValue(metadata); + + return claim; + } + + private boolean validatePublishClaim(Claim claim) { + if (Helper.isNullOrEmpty(claim.getTitle())) { + showError(getString(R.string.please_provide_title)); + return false; + } + if (Helper.isNullOrEmpty(claim.getName())) { + showError(getString(R.string.please_specify_address)); + return false; + } + if (!LbryUri.isNameValid(claim.getName())) { + showError(getString(R.string.address_invalid_characters)); + return false; + } + if (!editMode && Helper.claimNameExists(claim.getName())) { + showError(getString(R.string.address_already_used)); + return false; + } + + String publishFilePath = currentGalleryItem != null ? currentGalleryItem.getFilePath() : currentFilePath; + if (!editMode && Helper.isNullOrEmpty(publishFilePath) && Helper.isNullOrEmpty(transcodedFilePath)) { + showError(getString(R.string.no_file_selected)); + return false; + } + + return true; + } + + private void publishClaim(Claim claim) { + String finalFilePath = transcodedFilePath; + if (Helper.isNullOrEmpty(finalFilePath)) { + finalFilePath = currentGalleryItem != null ? currentGalleryItem.getFilePath() : currentFilePath; + } + saveInProgress = true; + PublishClaimTask task = new PublishClaimTask(claim, finalFilePath, progressPublish, new ClaimResultHandler() { + @Override + public void beforeStart() { + preSave(); + } + + @Override + public void onSuccess(Claim claimResult) { + postSave(); + + // Run the logPublish task + if (!BuildConfig.DEBUG) { + LogPublishTask logPublish = new LogPublishTask(claimResult); + logPublish.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + // publish done + Bundle bundle = new Bundle(); + bundle.putString("claim_id", claimResult.getClaimId()); + bundle.putString("claim_name", claimResult.getName()); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_PUBLISH, bundle); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.showMessage(R.string.publish_successful); + activity.openPublishesOnSuccessfulPublish(); + } + } + + @Override + public void onError(Exception error) { + showError(error.getMessage()); + postSave(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void preSave() { + saveInProgress = true; + + // disable input views + Helper.setViewEnabled(channelSpinner, false); + Helper.setViewEnabled(inputTitle, false); + Helper.setViewEnabled(inputDescription, false); + Helper.setViewEnabled(inputTagFilter, false); + Helper.setViewEnabled(inputAddress, false); + Helper.setViewEnabled(inputDeposit, false); + Helper.setViewEnabled(inputPrice, false); + Helper.setViewEnabled(inputOtherLicenseDescription, false); + Helper.setViewEnabled(switchPrice, false); + Helper.setViewEnabled(languageSpinner, false); + Helper.setViewEnabled(licenseSpinner, false); + Helper.setViewEnabled(priceCurrencySpinner, false); + Helper.setViewEnabled(linkGenerateAddress, false); + + Helper.setViewEnabled(linkShowExtraFields, false); + Helper.setViewEnabled(linkPublishCancel, false); + Helper.setViewEnabled(buttonPublish, false); + } + + private void postSave() { + Helper.setViewEnabled(channelSpinner, true); + Helper.setViewEnabled(inputTitle, true); + Helper.setViewEnabled(inputDescription, true); + Helper.setViewEnabled(inputTagFilter, false); + Helper.setViewEnabled(inputAddress, editMode ? false : true); + Helper.setViewEnabled(inputDeposit, true); + Helper.setViewEnabled(inputPrice, true); + Helper.setViewEnabled(inputOtherLicenseDescription, true); + Helper.setViewEnabled(switchPrice, true); + Helper.setViewEnabled(languageSpinner, true); + Helper.setViewEnabled(licenseSpinner, true); + Helper.setViewEnabled(priceCurrencySpinner, true); + Helper.setViewEnabled(linkGenerateAddress, true); + + Helper.setViewEnabled(linkShowExtraFields, true); + Helper.setViewEnabled(linkPublishCancel, true); + Helper.setViewEnabled(buttonPublish, true); + + saveInProgress = false; + } + + + @Override + public boolean shouldHideGlobalPlayer() { + return true; + } + + @Override + public boolean shouldSuspendGlobalPlayer() { + return true; + } + + @Override + public void onTagClicked(Tag tag, int customizeMode) { + if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_ADD) { + addTag(tag); + } else if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_REMOVE) { + removeTag(tag); + } + } + + public void setFilter(String filter) { + currentFilter = filter; + updateSuggestedTags(currentFilter, SUGGESTED_LIMIT, true); + } + private void checkNoAddedTags() { + Helper.setViewVisibility(noTagsView, addedTagsAdapter == null || addedTagsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + private void checkNoTagResults() { + Helper.setViewVisibility(noTagResultsView, suggestedTagsAdapter == null || suggestedTagsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + public void addTag(Tag tag) { + if (saveInProgress) { + return; + } + + if (addedTagsAdapter.getTags().contains(tag)) { + Snackbar.make(getView(), getString(R.string.tag_already_added, tag.getName()), Snackbar.LENGTH_LONG).show(); + return; + } + if (addedTagsAdapter.getItemCount() == 5) { + Snackbar.make(getView(), R.string.tag_limit_reached, Snackbar.LENGTH_LONG).show(); + return; + } + + addedTagsAdapter.addTag(tag); + if (suggestedTagsAdapter != null) { + suggestedTagsAdapter.removeTag(tag); + } + updateSuggestedTags(currentFilter, SUGGESTED_LIMIT, false); + + checkNoAddedTags(); + checkNoTagResults(); + } + public void removeTag(Tag tag) { + if (saveInProgress) { + return; + } + addedTagsAdapter.removeTag(tag); + updateSuggestedTags(currentFilter, SUGGESTED_LIMIT, false); + checkNoAddedTags(); + checkNoTagResults(); + } + private void updateSuggestedTags(String filter, int limit, boolean clearPrevious) { + UpdateSuggestedTagsTask task = new UpdateSuggestedTagsTask( + filter, + limit, + addedTagsAdapter, + suggestedTagsAdapter, + clearPrevious, + true, new UpdateSuggestedTagsTask.KnownTagsHandler() { + @Override + public void onSuccess(List tags) { + if (suggestedTagsAdapter == null) { + suggestedTagsAdapter = new TagListAdapter(tags, getContext()); + suggestedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD); + suggestedTagsAdapter.setClickListener(PublishFormFragment.this); + if (suggestedTagsList != null) { + suggestedTagsList.setAdapter(suggestedTagsAdapter); + } + } else { + suggestedTagsAdapter.setTags(tags); + } + + checkNoAddedTags(); + checkNoTagResults(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + @Override + public void onWalletBalanceUpdated(WalletBalance walletBalance) { + if (walletBalance != null && inlineDepositBalanceValue != null) { + inlineDepositBalanceValue.setText(Helper.shortCurrencyFormat(walletBalance.getAvailable().doubleValue())); + } + } + + private void setupInlineChannelCreator( + View container, + TextInputEditText inputChannelName, + TextInputEditText inputDeposit, + View inlineBalanceView, + TextView inlineBalanceValue, + View linkCancel, + MaterialButton buttonCreate, + View progressView) { + inputDeposit.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + Helper.setViewVisibility(inlineBalanceView, hasFocus ? View.VISIBLE : View.INVISIBLE); + } + }); + + linkCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Helper.setViewText(inputChannelName, null); + Helper.setViewText(inputDeposit, null); + Helper.setViewVisibility(container, View.GONE); + } + }); + + buttonCreate.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // validate deposit and channel name + String channelNameString = Helper.normalizeChannelName(Helper.getValue(inputChannelName.getText())); + Claim claimToSave = new Claim(); + claimToSave.setName(channelNameString); + String channelName = claimToSave.getName().startsWith("@") ? claimToSave.getName().substring(1) : claimToSave.getName(); + String depositString = Helper.getValue(inputDeposit.getText()); + if ("@".equals(channelName) || Helper.isNullOrEmpty(channelName)) { + showError(getString(R.string.please_enter_channel_name)); + return; + } + if (!LbryUri.isNameValid(channelName)) { + showError(getString(R.string.channel_name_invalid_characters)); + return; + } + if (Helper.channelExists(channelName)) { + showError(getString(R.string.channel_name_already_created)); + return; + } + + double depositAmount = 0; + try { + depositAmount = Double.valueOf(depositString); + } catch (NumberFormatException ex) { + // pass + showError(getString(R.string.please_enter_valid_deposit)); + return; + } + if (depositAmount == 0) { + String error = getResources().getQuantityString(R.plurals.min_deposit_required, depositAmount == 1 ? 1 : 2, String.valueOf(Helper.MIN_DEPOSIT)); + showError(error); + return; + } + if (Lbry.walletBalance == null || Lbry.walletBalance.getAvailable().doubleValue() < depositAmount) { + showError(getString(R.string.deposit_more_than_balance)); + return; + } + + ChannelCreateUpdateTask task = new ChannelCreateUpdateTask( + claimToSave, new BigDecimal(depositString), false, progressView, new ClaimResultHandler() { + @Override + public void beforeStart() { + Helper.setViewEnabled(inputChannelName, false); + Helper.setViewEnabled(inputDeposit, false); + Helper.setViewEnabled(buttonCreate, false); + Helper.setViewEnabled(linkCancel, false); + } + + @Override + public void onSuccess(Claim claimResult) { + if (!BuildConfig.DEBUG) { + LogPublishTask logPublishTask = new LogPublishTask(claimResult); + logPublishTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + // channel created + Bundle bundle = new Bundle(); + bundle.putString("claim_id", claimResult.getClaimId()); + bundle.putString("claim_name", claimResult.getName()); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_CHANNEL_CREATE, bundle); + + // add the claim to the channel list and set it as the selected item + channelSpinnerAdapter.add(claimResult); + channelSpinner.setSelection(channelSpinnerAdapter.getCount() - 1); + + Helper.setViewEnabled(inputChannelName, true); + Helper.setViewEnabled(inputDeposit, true); + Helper.setViewEnabled(buttonCreate, true); + Helper.setViewEnabled(linkCancel, true); + } + + @Override + public void onError(Exception error) { + Helper.setViewEnabled(inputChannelName, true); + Helper.setViewEnabled(inputDeposit, true); + Helper.setViewEnabled(buttonCreate, true); + Helper.setViewEnabled(linkCancel, true); + showError(error.getMessage()); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + Helper.setViewText(inlineBalanceValue, Helper.shortCurrencyFormat(Lbry.walletBalance.getAvailable().doubleValue())); + } + + private void showError(String message) { + Context context = getContext(); + if (context != null) { + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } + } + + private void checkUploadButton() { + + } + + @Override + public void onStoragePermissionGranted() { + if (launchPickerPending) { + launchPickerPending = false; + launchFilePicker(); + } + } + + @Override + public void onStoragePermissionRefused() { + showError(getString(R.string.storage_permission_rationale_images)); + launchPickerPending = false; + } + + @Override + public void onFilePicked(String filePath) { + if (Helper.isNullOrEmpty(filePath)) { + Snackbar.make(getView(), R.string.undetermined_image_filepath, Snackbar.LENGTH_LONG).setBackgroundTint( + ContextCompat.getColor(getContext(), R.color.red)).show(); + return; + } + + Context context = getContext(); + if (context != null) { + if (filePath.equalsIgnoreCase(lastSelectedThumbnailFile)) { + // previous selected cover was uploaded successfully + return; + } + + Uri fileUri = Uri.fromFile(new File(filePath)); + Glide.with(context.getApplicationContext()).load(fileUri).centerCrop().into(imageThumbnail); + uploadThumbnail(filePath); + } + } + + @Override + public void onFilePickerCancelled() { + // nothing to do here + // At some point in the future, allow file picking for publish file? + } + + private static class VideoProbeTask extends AsyncTask { + private String filePath; + private VideoProbeHandler handler; + public VideoProbeTask(String filePath, VideoProbeHandler handler) { + this.filePath = filePath; + this.handler = handler; + } + protected VideoInformation doInBackground(Void... params) { + try { + int code = FFprobe.execute(String.format("-v quiet -show_streams -select_streams v -print_format json -i \"%s\"", filePath)); + if (code == Config.RETURN_CODE_SUCCESS) { + String json = Config.getLastCommandOutput(); + JSONObject result = new JSONObject(json); + if (result.has("streams")) { + JSONArray streams = result.getJSONArray("streams"); + if (streams.length() > 0) { + JSONObject stream = streams.getJSONObject(0); + VideoInformation videoInformation = VideoInformation.fromJSONObject(stream); + return videoInformation; + } + } + } + } catch (JSONException ex) { + // pass + } + return null; + } + protected void onPostExecute(VideoInformation result) { + if (handler != null) { + handler.onVideoProbed(result); + } + } + + public interface VideoProbeHandler { + void onVideoProbed(VideoInformation result); + } + } + + private static class VideoTranscodeTask extends AsyncTask { + + private String filePath; + private String scaleFlag; + private String outputFilePath; + private boolean transcodeRequired; + private VideoTranscodeHandler handler; + + public VideoTranscodeTask(String filePath, String outputFilePath, String scaleFlag, boolean transcodeRequired, VideoTranscodeHandler handler) { + this.handler = handler; + this.filePath = filePath; + this.outputFilePath = outputFilePath; + this.scaleFlag = scaleFlag; + this.transcodeRequired = transcodeRequired; + } + + protected Boolean doInBackground(Void... params) { + String movFlagsCommand = String.format("-i \"%s\" -movflags +faststart \"%s\"", filePath, outputFilePath); + String command = transcodeRequired ? String.format( + "-i \"%s\" " + + "-c:v libx264 " + + "-c:a aac -b:a 128k " + + "%s " + + "-crf 27 -preset ultrafast " + + "-pix_fmt yuv420p " + + "-maxrate 5000K -bufsize 5000K " + + "-movflags +faststart \"%s\"", filePath, scaleFlag, outputFilePath) : movFlagsCommand; + + Config.enableStatisticsCallback(new StatisticsCallback() { + @Override + public void apply(Statistics statistics) { + publishProgress(statistics.getTime()); + } + }); + int code = FFmpeg.execute(command); + return code == Config.RETURN_CODE_SUCCESS; + } + + protected void onProgressUpdate(Integer... times) { + if (handler != null) { + for (Integer time : times) { + handler.onProgress(time); + } + } + } + + protected void onPostExecute(Boolean result) { + if (handler != null) { + if (result) { + handler.onSuccess(outputFilePath); + } else { + handler.onErrorOrCancelled(); + } + } + } + + public interface VideoTranscodeHandler { + void onProgress(int time); + void onSuccess(String outputFilePath); + void onErrorOrCancelled(); + } + } + + @Data + private static class VideoInformation { + private String codecName; + private int width; + private int height; + private int durationSeconds; + private long bitrate; + + private static int tryParseDuration(JSONObject streamObject) { + String durationString = Helper.getJSONString("duration", "0", streamObject); + double parsedDuration = Helper.parseDouble(durationString, 0); + if (parsedDuration > 0) { + return Double.valueOf(parsedDuration).intValue(); + } + + try { + if (streamObject.has("tags") && !streamObject.isNull("tags")) { + JSONObject tags = streamObject.getJSONObject("tags"); + String tagDurationString = Helper.getJSONString("DURATION", null, tags); + if (Helper.isNull(tagDurationString)) { + tagDurationString = Helper.getJSONString("duration", null, tags); + } + if (!Helper.isNullOrEmpty(tagDurationString) && tagDurationString.indexOf(':') > -1) { + String[] parts = tagDurationString.split(":"); + if (parts.length == 3) { + int hours = Helper.parseInt(parts[0], 0); + int minutes = Helper.parseInt(parts[1], 0); + int seconds = Helper.parseDouble(parts[2], 0).intValue(); + return (hours * 60 * 60) + (minutes * 60) + seconds; + } + } + + } + } catch (JSONException ex) { + return 0; + } + + return 0; + } + + public static VideoInformation fromJSONObject(JSONObject streamObject) { + VideoInformation info = new VideoInformation(); + info.setCodecName(Helper.getJSONString("codec_name", null, streamObject)); + info.setWidth(Helper.getJSONInt("width", 0, streamObject)); + info.setHeight(Helper.getJSONInt("height", 0, streamObject)); + info.setBitrate(Helper.getJSONLong("bit_rate", 0, streamObject)); + info.setDurationSeconds(tryParseDuration(streamObject)); + + return info; + } + } + + private static class CreateThumbnailTask extends AsyncTask { + private Context context; + private String filePath; + private String type; + private CreateThumbnailHandler handler; + private Exception error; + public CreateThumbnailTask(String filePath, String type, Context context, CreateThumbnailHandler handler) { + this.context = context; + this.type = type; + this.filePath = filePath; + this.handler = handler; + } + protected String doInBackground(Void... params) { + String thumbnailPath = null; + FileOutputStream os = null; + Bitmap thumbnail = null; + try { + File cacheDir = context.getExternalCacheDir(); + File thumbnailsDir = new File(String.format("%s/thumbnails", cacheDir.getAbsolutePath())); + if (!thumbnailsDir.isDirectory()) { + thumbnailsDir.mkdirs(); + } + + // save the thumbnail to the path + thumbnailPath = String.format("%s/%s.png", thumbnailsDir.getAbsolutePath(), Helper.makeid(8)); + if ("video".equals(type)) { + thumbnail = ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Video.Thumbnails.MINI_KIND); + } else { + Bitmap source = BitmapFactory.decodeFile(filePath); + // MINI_KIND dimensions + thumbnail = Bitmap.createScaledBitmap(source, 512, 384, false); + } + + os = new FileOutputStream(thumbnailPath); + thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os); + } catch (Exception ex) { + error = ex; + return null; + } finally { + Helper.closeCloseable(os); + } + + return thumbnailPath; + } + protected void onPostExecute(String thumbnailPath) { + if (handler != null) { + if (!Helper.isNullOrEmpty(thumbnailPath)) { + handler.onSuccess(thumbnailPath); + } else { + handler.onError(error); + } + } + } + + public interface CreateThumbnailHandler { + void onSuccess(String thumbnailPath); + void onError(Exception error); + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/publish/PublishFragment.java b/app/src/main/java/io/lbry/browser/ui/publish/PublishFragment.java new file mode 100644 index 00000000..af3a7eff --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/publish/PublishFragment.java @@ -0,0 +1,436 @@ +package io.lbry.browser.ui.publish; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.camera.core.Camera; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.CameraX; +import androidx.camera.core.Preview; +import androidx.camera.lifecycle.ProcessCameraProvider; +import androidx.camera.view.PreviewView; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.LifecycleOwner; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.snackbar.Snackbar; +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.GalleryGridAdapter; +import io.lbry.browser.listener.CameraPermissionListener; +import io.lbry.browser.listener.FilePickerListener; +import io.lbry.browser.listener.StoragePermissionListener; +import io.lbry.browser.model.GalleryItem; +import io.lbry.browser.model.NavMenuItem; +import io.lbry.browser.tasks.localdata.LoadGalleryItemsTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; + +public class PublishFragment extends BaseFragment implements + CameraPermissionListener, FilePickerListener, StoragePermissionListener { + + private PreviewView cameraPreview; + private RecyclerView galleryGrid; + private GalleryGridAdapter adapter; + private TextView noVideosLoaded; + private View loading; + + private View buttonRecord; + private View buttonTakePhoto; + private View buttonUpload; + + private boolean loadGalleryItemsPending; + private boolean launchFilePickerPending; + private boolean recordPending; + private boolean takePhotoPending; + private ListenableFuture cameraProviderFuture; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_publish, container, false); + + noVideosLoaded = root.findViewById(R.id.publish_grid_no_videos); + loading = root.findViewById(R.id.publish_grid_loading); + cameraPreview = root.findViewById(R.id.publish_camera_preview); + + Context context = getContext(); + galleryGrid = root.findViewById(R.id.publish_video_grid); + GridLayoutManager glm = new GridLayoutManager(context, 3); + galleryGrid.setLayoutManager(glm); + galleryGrid.addItemDecoration(new GalleryGridAdapter.GalleryGridItemDecoration( + 3, Helper.getScaledValue(3, context.getResources().getDisplayMetrics().density))); + + buttonRecord = root.findViewById(R.id.publish_record_button); + buttonTakePhoto = root.findViewById(R.id.publish_photo_button); + buttonUpload = root.findViewById(R.id.publish_upload_button); + + buttonRecord.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + checkCameraPermissionAndRecord(); + } + }); + buttonTakePhoto.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + checkCameraPermissionAndTakePhoto(); + } + }); + buttonUpload.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + checkStoragePermissionAndLaunchFilePicker(); + } + }); + + return root; + } + + private boolean cameraAvailable() { + Context context = getContext(); + return context != null && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY); + } + + private void showCameraPreview() { + buttonRecord.setBackgroundColor(Color.TRANSPARENT); + buttonTakePhoto.setBackgroundColor(Color.TRANSPARENT); + displayPreviewWithCameraX(); + } + + private void displayPreviewWithCameraX() { + Context context = getContext(); + if (context != null) { + cameraProviderFuture = ProcessCameraProvider.getInstance(context); + cameraProviderFuture.addListener(new Runnable() { + @Override + public void run() { + try { + ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); + if (cameraProvider != null) { + Preview preview = new Preview.Builder().build(); + CameraSelector cameraSelector = new CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build(); + + Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, preview); + preview.setSurfaceProvider(cameraPreview.createSurfaceProvider(camera.getCameraInfo())); + } + } catch (ExecutionException | InterruptedException ex) { + // pass + } + } + }, ContextCompat.getMainExecutor(context)); + } + } + + private void checkCameraPermissionAndRecord() { + if (!Lbry.SDK_READY) { + Snackbar.make(getView(), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + Context context = getContext(); + if (!MainActivity.hasPermission(Manifest.permission.CAMERA, context)) { + recordPending = true; + MainActivity.requestPermission( + Manifest.permission.CAMERA, + MainActivity.REQUEST_CAMERA_PERMISSION, + getString(R.string.camera_permission_rationale_record), + context, + true); + } else { + record(); + } + } + + private void checkCameraPermissionAndTakePhoto() { + if (!Lbry.SDK_READY) { + Snackbar.make(getView(), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + Context context = getContext(); + if (!MainActivity.hasPermission(Manifest.permission.CAMERA, context)) { + takePhotoPending = true; + MainActivity.requestPermission( + Manifest.permission.CAMERA, + MainActivity.REQUEST_CAMERA_PERMISSION, + getString(R.string.camera_permission_rationale_photo), + context, + true); + } else { + takePhoto(); + } + } + + private void takePhoto() { + Context context = getContext(); + if (context instanceof MainActivity) { + takePhotoPending = false; + ((MainActivity) context).requestTakePhoto(); + } + } + + private void record() { + Context context = getContext(); + if (context instanceof MainActivity) { + recordPending = false; + ((MainActivity) context).requestVideoCapture(); + } + } + + private void checkStoragePermissionAndLaunchFilePicker() { + if (!Lbry.SDK_READY) { + Snackbar.make(getView(), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + Context context = getContext(); + if (MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context)) { + launchFilePickerPending = false; + launchFilePicker(); + } else { + launchFilePickerPending = true; + MainActivity.requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, + MainActivity.REQUEST_STORAGE_PERMISSION, + getString(R.string.storage_permission_rationale_images), + context, + true); + } + } + + private void launchFilePicker() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity.startingFilePickerActivity = true; + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + ((MainActivity) context).startActivityForResult( + Intent.createChooser(intent, getString(R.string.upload_file)), + MainActivity.REQUEST_FILE_PICKER); + } + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + Helper.setWunderbarValue(null, context); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Publish", "Publish"); + activity.addCameraPermissionListener(this); + activity.addFilePickerListener(this); + activity.addStoragePermissionListener(this); + activity.hideFloatingWalletBalance(); + + + if (cameraAvailable() && MainActivity.hasPermission(Manifest.permission.CAMERA, context)) { + showCameraPreview(); + } + } + + checkStoragePermissionAndLoadVideos(); + } + + @SuppressLint("RestrictedApi") + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.removeCameraPermissionListener(this); + activity.removeStoragePermissionListener(this); + activity.showFloatingWalletBalance(); + if (!MainActivity.startingFilePickerActivity) { + activity.removeFilePickerListener(this); + } + } + CameraX.unbindAll(); + super.onStop(); + } + + private void checkStoragePermissionAndLoadVideos() { + Context context = getContext(); + if (MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, context)) { + loadGalleryItems(); + } else { + loadGalleryItemsPending = true; + MainActivity.requestPermission( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + MainActivity.REQUEST_STORAGE_PERMISSION, + getString(R.string.storage_permission_rationale_download), + context, + true); + } + } + + private void loadGalleryItems() { + Context context = getContext(); + Helper.setViewVisibility(noVideosLoaded, View.GONE); + LoadGalleryItemsTask task = new LoadGalleryItemsTask(loading, context, new LoadGalleryItemsTask.LoadGalleryHandler() { + @Override + public void onItemLoaded(GalleryItem item) { + if (context != null) { + if (adapter == null) { + adapter = new GalleryGridAdapter(Arrays.asList(item), context); + adapter.setListener(new GalleryGridAdapter.GalleryItemClickListener() { + @Override + public void onGalleryItemClicked(GalleryItem item) { + if (!Lbry.SDK_READY) { + Snackbar.make(getView(), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + Context context = getContext(); + if (context instanceof MainActivity) { + Map params = new HashMap<>(); + params.put("galleryItem", item); + params.put("suggestedUrl", getSuggestedPublishUrl()); + ((MainActivity) context).openFragment(PublishFormFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH, params); + } + } + }); + } else { + adapter.addItem(item); + } + + if (galleryGrid.getAdapter() == null) { + galleryGrid.setAdapter(adapter); + } + Helper.setViewVisibility(loading, adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + } + + @Override + public void onAllItemsLoaded(List items) { + if (context != null) { + if (adapter == null) { + adapter = new GalleryGridAdapter(items, context); + } else { + adapter.addItems(items); + } + + if (galleryGrid.getAdapter() == null) { + galleryGrid.setAdapter(adapter); + } + } + checkNoVideosLoaded(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void checkNoVideosLoaded() { + Helper.setViewVisibility(noVideosLoaded, adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + + @Override + public void onCameraPermissionGranted() { + if (recordPending) { + // record video + record(); + } else if (takePhotoPending) { + // take a photo + takePhoto(); + } + } + + @Override + public void onCameraPermissionRefused() { + if (takePhotoPending) { + takePhotoPending = false; + Snackbar.make(getView(), R.string.camera_permission_rationale_photo, Toast.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + return; + } + + recordPending = false; + Snackbar.make(getView(), R.string.camera_permission_rationale_record, Toast.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } + + @Override + public void onRecordAudioPermissionGranted() { + + } + + @Override + public void onRecordAudioPermissionRefused() { + + } + + @Override + public void onStoragePermissionGranted() { + if (loadGalleryItemsPending) { + loadGalleryItemsPending = false; + loadGalleryItems(); + } + if (launchFilePickerPending) { + launchFilePickerPending = false; + launchFilePicker(); + } + } + + @Override + public void onStoragePermissionRefused() { + Snackbar.make(getView(), R.string.storage_permission_rationale_videos, Snackbar.LENGTH_LONG).setBackgroundTint( + ContextCompat.getColor(getContext(), R.color.red) + ).show(); + } + + public String getSuggestedPublishUrl() { + Map params = getParams(); + if (params != null && params.containsKey("suggestedUrl")) { + return (String) params.get("suggestedUrl"); + } + return null; + } + + @Override + public boolean shouldHideGlobalPlayer() { + return true; + } + + @Override + public boolean shouldSuspendGlobalPlayer() { + return true; + } + + @Override + public void onFilePicked(String filePath) { + Context context = getContext(); + if (context instanceof MainActivity) { + Map params = new HashMap<>(); + params.put("directFilePath", filePath); + params.put("suggestedUrl", getSuggestedPublishUrl()); + ((MainActivity) context).openFragment(PublishFormFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH, params); + } + } + + @Override + public void onFilePickerCancelled() { + + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/publish/PublishesFragment.java b/app/src/main/java/io/lbry/browser/ui/publish/PublishesFragment.java new file mode 100644 index 00000000..0ba048ff --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/publish/PublishesFragment.java @@ -0,0 +1,320 @@ +package io.lbry.browser.ui.publish; + +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ProgressBar; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.view.ActionMode; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.ClaimListAdapter; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.listener.SelectionModeListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.NavMenuItem; +import io.lbry.browser.tasks.claim.AbandonHandler; +import io.lbry.browser.tasks.claim.AbandonStreamTask; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimListTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; + +public class PublishesFragment extends BaseFragment implements ActionMode.Callback, SelectionModeListener, SdkStatusListener { + + private Button buttonNewPublish; + private FloatingActionButton fabNewPublish; + private ActionMode actionMode; + private View emptyView; + private View layoutSdkInitializing; + private ProgressBar loading; + private ProgressBar bigLoading; + private RecyclerView contentList; + private ClaimListAdapter adapter; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_publishes, container, false); + + buttonNewPublish = root.findViewById(R.id.publishes_create_button); + fabNewPublish = root.findViewById(R.id.publishes_fab_new_publish); + buttonNewPublish.setOnClickListener(newPublishClickListener); + fabNewPublish.setOnClickListener(newPublishClickListener); + + emptyView = root.findViewById(R.id.publishes_empty_container); + layoutSdkInitializing = root.findViewById(R.id.container_sdk_initializing); + contentList = root.findViewById(R.id.publishes_list); + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + contentList.setLayoutManager(llm); + loading = root.findViewById(R.id.publishes_list_loading); + bigLoading = root.findViewById(R.id.publishes_list_big_loading); + + layoutSdkInitializing.setVisibility(Lbry.SDK_READY ? View.GONE : View.VISIBLE); + + return root; + } + + private View.OnClickListener newPublishClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).openFragment(PublishFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH); + } + } + }; + + @Override + public void onStart() { + super.onStart(); + Context context = getContext(); + if (context != null) { + MainActivity activity = (MainActivity) context; + activity.hideFloatingWalletBalance(); + } + } + + @Override + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.showFloatingWalletBalance(); + } + super.onStop(); + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.setWunderbarValue(null); + LbryAnalytics.setCurrentScreen(activity, "Publishes", "Publishes"); + } + + if (!Lbry.SDK_READY) { + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.addSdkStatusListener(this); + } + } else { + onSdkReady(); + } + } + + public void onSdkReady() { + Helper.setViewVisibility(layoutSdkInitializing, View.GONE); + Helper.setViewVisibility(fabNewPublish, View.VISIBLE); + if (adapter != null && contentList != null) { + contentList.setAdapter(adapter); + } + fetchPublishes(); + } + + public View getLoading() { + return (adapter == null || adapter.getItemCount() == 0) ? bigLoading : loading; + } + + private void checkNoPublishes() { + Helper.setViewVisibility(emptyView, adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + + private void fetchPublishes() { + Helper.setViewVisibility(emptyView, View.GONE); + ClaimListTask task = new ClaimListTask(Arrays.asList(Claim.TYPE_STREAM, Claim.TYPE_REPOST), getLoading(), new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + Lbry.ownClaims = Helper.filterDeletedClaims(new ArrayList<>(claims)); + Context context = getContext(); + if (adapter == null) { + adapter = new ClaimListAdapter(claims, context); + adapter.setCanEnterSelectionMode(true); + adapter.setSelectionModeListener(PublishesFragment.this); + adapter.setListener(new ClaimListAdapter.ClaimListItemListener() { + @Override + public void onClaimClicked(Claim claim) { + if (context instanceof MainActivity) { + ((MainActivity) context).openFileClaim(claim); + } + } + }); + if (contentList != null) { + contentList.setAdapter(adapter); + } + } else { + adapter.setItems(claims); + } + + checkNoPublishes(); + } + + @Override + public void onError(Exception error) { + checkNoPublishes(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void onEnterSelectionMode() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.startSupportActionMode(this); + } + } + public void onItemSelectionToggled() { + if (actionMode != null) { + actionMode.setTitle(String.valueOf(adapter.getSelectedCount())); + actionMode.invalidate(); + } + } + public void onExitSelectionMode() { + if (actionMode != null) { + actionMode.finish(); + } + } + + @Override + public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { + this.actionMode = actionMode; + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (!activity.isDarkMode()) { + activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + } + } + + actionMode.getMenuInflater().inflate(R.menu.menu_claim_list, menu); + return true; + } + @Override + public void onDestroyActionMode(ActionMode actionMode) { + if (adapter != null) { + adapter.clearSelectedItems(); + adapter.setInSelectionMode(false); + adapter.notifyDataSetChanged(); + } + Context context = getContext(); + if (context != null) { + MainActivity activity = (MainActivity) context; + if (!activity.isDarkMode()) { + activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + } + this.actionMode = null; + } + + @Override + public boolean onPrepareActionMode(androidx.appcompat.view.ActionMode actionMode, Menu menu) { + int selectionCount = adapter != null ? adapter.getSelectedCount() : 0; + menu.findItem(R.id.action_edit).setVisible(selectionCount == 1); + return true; + } + + @Override + public boolean onActionItemClicked(androidx.appcompat.view.ActionMode actionMode, MenuItem menuItem) { + if (R.id.action_edit == menuItem.getItemId()) { + if (adapter != null && adapter.getSelectedCount() > 0) { + Claim claim = adapter.getSelectedItems().get(0); + // start channel editor with the claim + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).openPublishForm(claim); + } + + actionMode.finish(); + return true; + } + } + if (R.id.action_delete == menuItem.getItemId()) { + if (adapter != null && adapter.getSelectedCount() > 0) { + final List selectedClaims = new ArrayList<>(adapter.getSelectedItems()); + String message = getResources().getQuantityString(R.plurals.confirm_delete_publishes, selectedClaims.size()); + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()). + setTitle(R.string.delete_selection). + setMessage(message) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + handleDeleteSelectedClaims(selectedClaims); + } + }).setNegativeButton(R.string.no, null); + builder.show(); + return true; + } + } + + return false; + } + + private void handleDeleteSelectedClaims(List selectedClaims) { + List claimIds = new ArrayList<>(); + + for (Claim claim : selectedClaims) { + claimIds.add(claim.getClaimId()); + } + + if (actionMode != null) { + actionMode.finish(); + } + + Helper.setViewVisibility(contentList, View.INVISIBLE); + Helper.setViewVisibility(fabNewPublish, View.INVISIBLE); + AbandonStreamTask task = new AbandonStreamTask(claimIds, bigLoading, new AbandonHandler() { + @Override + public void onComplete(List successfulClaimIds, List failedClaimIds, List errors) { + View root = getView(); + if (root != null) { + if (failedClaimIds.size() > 0) { + Snackbar.make(root, R.string.one_or_more_publishes_failed_abandon, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } else if (successfulClaimIds.size() == claimIds.size()) { + try { + String message = getResources().getQuantityString(R.plurals.publishes_deleted, successfulClaimIds.size()); + Snackbar.make(root, message, Snackbar.LENGTH_LONG).show(); + } catch (IllegalStateException ex) { + // pass + } + } + } + + Lbry.abandonedClaimIds.addAll(successfulClaimIds); + if (adapter != null) { + adapter.setItems(Helper.filterDeletedClaims(adapter.getItems())); + } + + Helper.setViewVisibility(contentList, View.VISIBLE); + Helper.setViewVisibility(fabNewPublish, View.VISIBLE); + checkNoPublishes(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/verification/EmailVerificationFragment.java b/app/src/main/java/io/lbry/browser/ui/verification/EmailVerificationFragment.java new file mode 100644 index 00000000..3721ca27 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/verification/EmailVerificationFragment.java @@ -0,0 +1,201 @@ +package io.lbry.browser.ui.verification; + +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import io.lbry.browser.R; +import io.lbry.browser.listener.SignInListener; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.tasks.verification.CheckUserEmailVerifiedTask; +import io.lbry.browser.tasks.verification.EmailNewTask; +import io.lbry.browser.tasks.verification.EmailResendTask; +import io.lbry.browser.utils.Helper; +import lombok.Setter; + +public class EmailVerificationFragment extends Fragment { + + @Setter + private SignInListener listener; + private View layoutCollect; + private View layoutVerify; + private ProgressBar emailAddProgress; + private TextView textAddedEmail; + private TextInputEditText inputEmail; + private TextInputLayout inputLayoutEmail; + private MaterialButton buttonContinue; + private MaterialButton buttonResend; + private View buttonEdit; + + private String currentEmail; + + private ScheduledExecutorService emailVerifyCheckScheduler; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_verification_email, container, false); + + layoutCollect = root.findViewById(R.id.verification_email_collect_container); + layoutVerify = root.findViewById(R.id.verification_email_verify_container); + inputEmail = root.findViewById(R.id.verification_email_input); + inputLayoutEmail = root.findViewById(R.id.verification_email_input_layout); + emailAddProgress = root.findViewById(R.id.verification_email_add_progress); + textAddedEmail = root.findViewById(R.id.verification_email_added_address); + buttonContinue = root.findViewById(R.id.verification_email_continue_button); + buttonResend = root.findViewById(R.id.verification_email_resend_button); + buttonEdit = root.findViewById(R.id.verification_email_edit_button); + + layoutCollect.setVisibility(View.VISIBLE); + + inputEmail.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + String layoutHint = !hasFocus ? "" : getString(R.string.email); + inputLayoutEmail.setHint(layoutHint); + } + }); + buttonContinue.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + addEmail(); + } + }); + buttonEdit.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + editEmail(); + } + }); + buttonResend.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + resendEmail(); + } + }); + + return root; + } + + private void addEmail() { + currentEmail = Helper.getValue(inputEmail.getText()); + if (Helper.isNullOrEmpty(currentEmail) || currentEmail.indexOf("@") == -1) { + Snackbar.make(getView(), R.string.provide_valid_email, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + return; + } + + EmailNewTask task = new EmailNewTask(currentEmail, emailAddProgress, new EmailNewTask.EmailNewHandler() { + @Override + public void beforeStart() { + Helper.setViewVisibility(buttonContinue, View.INVISIBLE); + } + + @Override + public void onSuccess() { + layoutCollect.setVisibility(View.GONE); + layoutVerify.setVisibility(View.VISIBLE); + Helper.setViewText(textAddedEmail, currentEmail); + if (listener != null) { + listener.onEmailAdded(currentEmail); + } + scheduleEmailVerify(); + + Helper.setViewVisibility(buttonContinue, View.VISIBLE); + } + + @Override + public void onEmailExists() { + // TODO: Update wording based on email already existing + } + + @Override + public void onError(Exception error) { + Snackbar.make(getView(), error.getMessage(), Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + Helper.setViewVisibility(buttonContinue, View.VISIBLE); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void scheduleEmailVerify() { + emailVerifyCheckScheduler = Executors.newSingleThreadScheduledExecutor(); + emailVerifyCheckScheduler.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + checkEmailVerified(); + } + }, 5, 5, TimeUnit.SECONDS); + } + + private void checkEmailVerified() { + CheckUserEmailVerifiedTask task = new CheckUserEmailVerifiedTask(new CheckUserEmailVerifiedTask.CheckUserEmailVerifiedHandler() { + @Override + public void onUserEmailVerified() { + if (listener != null) { + listener.onEmailVerified(); + } + layoutCollect.setVisibility(View.GONE); + layoutVerify.setVisibility(View.GONE); + if (emailVerifyCheckScheduler != null) { + emailVerifyCheckScheduler.shutdownNow(); + emailVerifyCheckScheduler = null; + } + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void editEmail() { + if (emailVerifyCheckScheduler != null) { + emailVerifyCheckScheduler.shutdownNow(); + emailVerifyCheckScheduler = null; + } + + if (listener != null) { + listener.onEmailEdit(); + } + layoutVerify.setVisibility(View.GONE); + layoutCollect.setVisibility(View.VISIBLE); + } + + private void resendEmail() { + EmailResendTask task = new EmailResendTask(currentEmail, null, new GenericTaskHandler() { + @Override + public void beforeStart() { + Helper.setViewEnabled(buttonResend, false); + } + + @Override + public void onSuccess() { + Snackbar.make(getView(), R.string.please_follow_instructions, Snackbar.LENGTH_LONG).show(); + Helper.setViewEnabled(buttonResend, true); + } + + @Override + public void onError(Exception error) { + Snackbar.make(getView(), error.getMessage(), Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + Helper.setViewEnabled(buttonResend, true); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/verification/ManualVerificationFragment.java b/app/src/main/java/io/lbry/browser/ui/verification/ManualVerificationFragment.java new file mode 100644 index 00000000..e901cb13 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/verification/ManualVerificationFragment.java @@ -0,0 +1,36 @@ +package io.lbry.browser.ui.verification; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import io.lbry.browser.R; +import io.lbry.browser.listener.SignInListener; +import io.lbry.browser.utils.Helper; +import lombok.Setter; + +public class ManualVerificationFragment extends Fragment { + @Setter + private SignInListener listener; + + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_verification_manual, container, false); + + Helper.applyHtmlForTextView((TextView) root.findViewById(R.id.verification_manual_discord_verify)); + root.findViewById(R.id.verification_manual_continue_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) { + listener.onManualVerifyContinue(); + } + } + }); + + return root; + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/verification/PhoneVerificationFragment.java b/app/src/main/java/io/lbry/browser/ui/verification/PhoneVerificationFragment.java new file mode 100644 index 00000000..72789af0 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/verification/PhoneVerificationFragment.java @@ -0,0 +1,173 @@ +package io.lbry.browser.ui.verification; + +import android.content.Context; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; +import androidx.fragment.app.Fragment; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.hbb20.CountryCodePicker; + +import io.lbry.browser.R; +import io.lbry.browser.listener.SignInListener; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.tasks.verification.PhoneNewVerifyTask; +import io.lbry.browser.utils.Helper; +import lombok.Setter; + +public class PhoneVerificationFragment extends Fragment { + @Setter + private SignInListener listener; + + private View layoutCollect; + private View layoutVerify; + private MaterialButton continueButton; + private MaterialButton verifyButton; + private View editButton; + private TextView textVerifyParagraph; + private CountryCodePicker countryCodePicker; + private EditText inputPhoneNumber; + private EditText inputVerificationCode; + private ProgressBar newLoading; + private ProgressBar verifyLoading; + + private String currentCountryCode; + private String currentPhoneNumber; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_verification_phone, container, false); + + layoutCollect = root.findViewById(R.id.verification_phone_collect_container); + layoutVerify = root.findViewById(R.id.verification_phone_verify_container); + continueButton = root.findViewById(R.id.verification_phone_continue_button); + verifyButton = root.findViewById(R.id.verification_phone_verify_button); + editButton = root.findViewById(R.id.verification_phone_edit_button); + textVerifyParagraph = root.findViewById(R.id.verification_phone_verify_paragraph); + + countryCodePicker = root.findViewById(R.id.verification_phone_country_code); + inputPhoneNumber = root.findViewById(R.id.verification_phone_input); + inputVerificationCode = root.findViewById(R.id.verification_phone_code_input); + + newLoading = root.findViewById(R.id.verification_phone_new_progress); + verifyLoading = root.findViewById(R.id.verification_phone_verify_progress); + + Context context = getContext(); + countryCodePicker.setTypeFace(ResourcesCompat.getFont(context, R.font.inter_light)); + countryCodePicker.registerCarrierNumberEditText(inputPhoneNumber); + + continueButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + currentCountryCode = countryCodePicker.getSelectedCountryCode(); + currentPhoneNumber = Helper.getValue(inputPhoneNumber.getText()); + + if (Helper.isNullOrEmpty(currentPhoneNumber) || !countryCodePicker.isValidFullNumber()) { + Snackbar.make(getView(), R.string.please_enter_valid_phone, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + return; + } + + addPhoneNumber(); + } + }); + + verifyButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String code = Helper.getValue(inputVerificationCode.getText()); + if (Helper.isNullOrEmpty(code)) { + Snackbar.make(getView(), R.string.please_enter_verification_code, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + return; + } + verifyPhoneNumber(code); + } + }); + + editButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + layoutVerify.setVisibility(View.GONE); + layoutCollect.setVisibility(View.VISIBLE); + } + }); + + return root; + } + + private void addPhoneNumber() { + PhoneNewVerifyTask task = new PhoneNewVerifyTask(currentCountryCode, currentPhoneNumber, null, newLoading, new GenericTaskHandler() { + @Override + public void beforeStart() { + Helper.setViewEnabled(continueButton, false); + Helper.setViewVisibility(continueButton, View.GONE); + } + + @Override + public void onSuccess() { + if (listener != null) { + listener.onPhoneAdded(currentCountryCode, currentPhoneNumber); + } + + Helper.setViewText(textVerifyParagraph, getString(R.string.enter_phone_verify_code, countryCodePicker.getFullNumberWithPlus())); + Helper.setViewVisibility(layoutCollect, View.GONE); + Helper.setViewVisibility(layoutVerify, View.VISIBLE); + Helper.setViewEnabled(continueButton, true); + Helper.setViewVisibility(continueButton, View.VISIBLE); + } + + @Override + public void onError(Exception error) { + if (error != null && getView() != null) { + Snackbar.make(getView(), error.getMessage(), Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } + Helper.setViewEnabled(continueButton, true); + Helper.setViewVisibility(continueButton, View.VISIBLE); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void verifyPhoneNumber(String verificationCode) { + PhoneNewVerifyTask task = new PhoneNewVerifyTask(currentCountryCode, currentPhoneNumber, verificationCode, verifyLoading, new GenericTaskHandler() { + @Override + public void beforeStart() { + Helper.setViewEnabled(verifyButton, false); + Helper.setViewEnabled(editButton, false); + } + + @Override + public void onSuccess() { + if (listener != null) { + listener.onPhoneVerified(); + } + Helper.setViewEnabled(verifyButton, true); + Helper.setViewEnabled(editButton, true); + } + + @Override + public void onError(Exception error) { + if (getView() != null && error != null) { + Snackbar.make(getView(), error.getMessage(), Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } + Helper.setViewEnabled(verifyButton, true); + Helper.setViewEnabled(editButton, true); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/verification/WalletVerificationFragment.java b/app/src/main/java/io/lbry/browser/ui/verification/WalletVerificationFragment.java new file mode 100644 index 00000000..bece6300 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/verification/WalletVerificationFragment.java @@ -0,0 +1,235 @@ +package io.lbry.browser.ui.verification; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.TextInputEditText; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.listener.WalletSyncListener; +import io.lbry.browser.model.WalletSync; +import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler; +import io.lbry.browser.tasks.wallet.SyncApplyTask; +import io.lbry.browser.tasks.wallet.SyncGetTask; +import io.lbry.browser.tasks.wallet.SyncSetTask; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.Lbryio; +import io.lbry.lbrysdk.Utils; +import lombok.Setter; + +public class WalletVerificationFragment extends Fragment { + + @Setter + private WalletSyncListener listener = null; + private ProgressBar loading; + private TextView textLoading; + private View inputArea; + private MaterialButton doneButton; + private TextInputEditText inputPassword; + private WalletSync currentWalletSync; + private boolean verificationStarted; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_verification_wallet, container, false); + + loading = root.findViewById(R.id.verification_wallet_loading_progress); + textLoading = root.findViewById(R.id.verification_wallet_loading_text); + inputArea = root.findViewById(R.id.verification_wallet_input_area); + doneButton = root.findViewById(R.id.verification_wallet_done_button); + inputPassword = root.findViewById(R.id.verification_wallet_password_input); + + doneButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String password = Helper.getValue(inputPassword.getText()); + if (Helper.isNullOrEmpty(password)) { + showError(getString(R.string.please_enter_your_password)); + return; + } + if (listener != null) { + listener.onWalletSyncProcessing(); + } + processExistingWalletWithPassword(password); + } + }); + + loading.setVisibility(View.VISIBLE); + inputArea.setVisibility(View.GONE); + + return root; + } + + @Override + public void onResume() { + super.onResume(); + start(); + } + + public void start() { + if (verificationStarted) { + return; + } + if (listener != null) { + listener.onWalletSyncProcessing(); + } + + verificationStarted = true; + Helper.setViewVisibility(loading, View.VISIBLE); + Helper.setViewVisibility(textLoading, View.VISIBLE); + String password = Utils.getSecureValue(MainActivity.SECURE_VALUE_KEY_SAVED_PASSWORD, getContext(), Lbry.KEYSTORE); + // start verification process + SyncGetTask task = new SyncGetTask(password, false, null, new DefaultSyncTaskHandler() { + @Override + public void onSyncGetSuccess(WalletSync walletSync) { + currentWalletSync = walletSync; + Lbryio.lastRemoteHash = walletSync.getHash(); + processExistingWallet(walletSync); + } + + @Override + public void onSyncGetWalletNotFound() { + // no wallet found, get sync apply data and run the process + processNewWallet(); + } + @Override + public void onSyncGetError(Exception error) { + // try again + Helper.setViewVisibility(loading, View.GONE); + Helper.setViewText(textLoading, error.getMessage()); + showError(error.getMessage()); + if (listener != null) { + listener.onWalletSyncFailed(error); + } + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void processExistingWallet(WalletSync walletSync) { + // Try first sync apply + SyncApplyTask applyTask = new SyncApplyTask("", walletSync.getData(), null, new DefaultSyncTaskHandler() { + @Override + public void onSyncApplySuccess(String hash, String data) { + // check if local and remote hash are different, and then run sync set + Utils.setSecureValue(MainActivity.SECURE_VALUE_KEY_SAVED_PASSWORD, "", getContext(), Lbry.KEYSTORE); + if (!hash.equalsIgnoreCase(Lbryio.lastRemoteHash) && !Helper.isNullOrEmpty(Lbryio.lastRemoteHash)) { + new SyncSetTask(Lbryio.lastRemoteHash, hash, data, null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + if (listener != null) { + listener.onWalletSyncEnabled(); + } + } + + @Override + public void onSyncApplyError(Exception error) { + // failed, request the user to enter a password + Helper.setViewVisibility(loading, View.GONE); + Helper.setViewVisibility(textLoading, View.GONE); + Helper.setViewVisibility(inputArea, View.VISIBLE); + if (listener != null) { + listener.onWalletSyncWaitingForInput(); + } + } + }); + applyTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void processExistingWalletWithPassword(String password) { + Helper.setViewVisibility(loading, View.VISIBLE); + Helper.setViewVisibility(textLoading, View.VISIBLE); + Helper.setViewVisibility(inputArea, View.GONE); + + if (currentWalletSync == null) { + showError(getString(R.string.wallet_sync_op_failed)); + Helper.setViewText(textLoading, R.string.wallet_sync_op_failed); + return; + } + + Helper.setViewText(textLoading, R.string.apply_wallet_data); + SyncApplyTask applyTask = new SyncApplyTask(password, currentWalletSync.getData(), null, new DefaultSyncTaskHandler() { + @Override + public void onSyncApplySuccess(String hash, String data) { + Utils.setSecureValue(MainActivity.SECURE_VALUE_KEY_SAVED_PASSWORD, password, getContext(), Lbry.KEYSTORE); + // check if local and remote hash are different, and then run sync set + if (!hash.equalsIgnoreCase(Lbryio.lastRemoteHash) && !Helper.isNullOrEmpty(Lbryio.lastRemoteHash)) { + new SyncSetTask(Lbryio.lastRemoteHash, hash, data, null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + if (listener != null) { + listener.onWalletSyncEnabled(); + } + } + + @Override + public void onSyncApplyError(Exception error) { + // failed, request the user to enter a password + showError(error.getMessage()); + Helper.setViewVisibility(loading, View.GONE); + Helper.setViewVisibility(textLoading, View.GONE); + Helper.setViewVisibility(inputArea, View.VISIBLE); + if (listener != null) { + listener.onWalletSyncWaitingForInput(); + } + } + }); + applyTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void processNewWallet() { + SyncApplyTask fetchTask = new SyncApplyTask(true, null, new DefaultSyncTaskHandler() { + @Override + public void onSyncApplySuccess(String hash, String data) { createNewRemoteSync(hash, data); } + @Override + public void onSyncApplyError(Exception error) { + showError(error.getMessage()); + Helper.setViewVisibility(loading, View.GONE); + Helper.setViewText(textLoading, R.string.wallet_sync_op_failed); + if (listener != null) { + listener.onWalletSyncFailed(error); + } + } + }); + fetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void createNewRemoteSync(String hash, String data) { + SyncSetTask setTask = new SyncSetTask("", hash, data, new DefaultSyncTaskHandler() { + @Override + public void onSyncSetSuccess(String hash) { + Lbryio.lastRemoteHash = hash; + if (listener != null) { + listener.onWalletSyncEnabled(); + } + } + + @Override + public void onSyncSetError(Exception error) { + showError(error.getMessage()); + Helper.setViewVisibility(loading, View.GONE); + Helper.setViewText(textLoading, R.string.wallet_sync_op_failed); + if (listener != null) { + listener.onWalletSyncFailed(error); + } + } + }); + setTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void showError(String message) { + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).setBackgroundTint( + getResources().getColor(R.color.red) + ).show(); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/wallet/InvitesFragment.java b/app/src/main/java/io/lbry/browser/ui/wallet/InvitesFragment.java new file mode 100644 index 00000000..a718f106 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/wallet/InvitesFragment.java @@ -0,0 +1,572 @@ +package io.lbry.browser.ui.wallet; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatSpinner; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.BuildConfig; +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.InlineChannelSpinnerAdapter; +import io.lbry.browser.adapter.InviteeListAdapter; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.listener.WalletBalanceListener; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.model.lbryinc.Invitee; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimListTask; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.tasks.claim.ChannelCreateUpdateTask; +import io.lbry.browser.tasks.claim.ClaimResultHandler; +import io.lbry.browser.tasks.lbryinc.LogPublishTask; +import io.lbry.browser.tasks.lbryinc.FetchInviteStatusTask; +import io.lbry.browser.tasks.lbryinc.FetchReferralCodeTask; +import io.lbry.browser.tasks.lbryinc.InviteByEmailTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; + +public class InvitesFragment extends BaseFragment implements SdkStatusListener, WalletBalanceListener { + + private static final String INVITE_LINK_FORMAT = "https://lbry.tv/$/invite/%s:%s"; + + private boolean fetchingChannels; + private View layoutAccountDriver; + private View layoutSdkInitializing; + private TextView textLearnMoreLink; + private MaterialButton buttonGetStarted; + + private View buttonCopyInviteLink; + private TextView textInviteLink; + private TextInputLayout layoutInputEmail; + private TextInputEditText inputEmail; + private MaterialButton buttonInviteByEmail; + + private RecyclerView inviteHistoryList; + private InviteeListAdapter inviteHistoryAdapter; + private InlineChannelSpinnerAdapter channelSpinnerAdapter; + private AppCompatSpinner channelSpinner; + private View progressLoadingChannels; + private View progressLoadingInviteByEmail; + private View progressLoadingStatus; + + private View inlineChannelCreator; + private TextInputEditText inlineChannelCreatorInputName; + private TextInputEditText inlineChannelCreatorInputDeposit; + private View inlineChannelCreatorInlineBalance; + private TextView inlineChannelCreatorInlineBalanceValue; + private View inlineChannelCreatorCancelLink; + private View inlineChannelCreatorProgress; + private MaterialButton inlineChannelCreatorCreateButton; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_invites, container, false); + + layoutAccountDriver = root.findViewById(R.id.invites_account_driver_container); + layoutSdkInitializing = root.findViewById(R.id.container_sdk_initializing); + textLearnMoreLink = root.findViewById(R.id.invites_account_driver_learn_more); + buttonGetStarted = root.findViewById(R.id.invites_get_started_button); + + textInviteLink = root.findViewById(R.id.invites_invite_link); + buttonCopyInviteLink = root.findViewById(R.id.invites_copy_invite_link); + layoutInputEmail = root.findViewById(R.id.invites_email_input_layout); + inputEmail = root.findViewById(R.id.invites_email_input); + buttonInviteByEmail = root.findViewById(R.id.invites_email_button); + + progressLoadingChannels = root.findViewById(R.id.invites_loading_channels_progress); + progressLoadingInviteByEmail = root.findViewById(R.id.invites_loading_invite_by_email_progress); + progressLoadingStatus = root.findViewById(R.id.invites_loading_status_progress); + + inviteHistoryList = root.findViewById(R.id.invite_history_list); + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + inviteHistoryList.setLayoutManager(llm); + + channelSpinner = root.findViewById(R.id.invites_channel_spinner); + + inlineChannelCreator = root.findViewById(R.id.container_inline_channel_form_create); + inlineChannelCreatorInputName = root.findViewById(R.id.inline_channel_form_input_name); + inlineChannelCreatorInputDeposit = root.findViewById(R.id.inline_channel_form_input_deposit); + inlineChannelCreatorInlineBalance = root.findViewById(R.id.inline_channel_form_inline_balance_container); + inlineChannelCreatorInlineBalanceValue = root.findViewById(R.id.inline_channel_form_inline_balance_value); + inlineChannelCreatorProgress = root.findViewById(R.id.inline_channel_form_create_progress); + inlineChannelCreatorCancelLink = root.findViewById(R.id.inline_channel_form_cancel_link); + inlineChannelCreatorCreateButton = root.findViewById(R.id.inline_channel_form_create_button); + + initUi(); + + return root; + } + + private void initUi() { + layoutAccountDriver.setVisibility(Lbryio.isSignedIn() ? View.GONE : View.VISIBLE); + layoutSdkInitializing.setVisibility(Lbry.SDK_READY ? View.GONE : View.VISIBLE); + Helper.applyHtmlForTextView(textLearnMoreLink); + + inputEmail.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + layoutInputEmail.setHint(hasFocus ? getString(R.string.email) : + Helper.getValue(inputEmail.getText()).length() > 0 ? + getString(R.string.email) : getString(R.string.invite_email_placeholder)); + } + }); + inputEmail.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + Helper.setViewEnabled(buttonInviteByEmail, charSequence.length() > 0); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + buttonInviteByEmail.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String email = Helper.getValue(inputEmail.getText()); + if (email.indexOf("@") == -1) { + showError(getString(R.string.provide_valid_email)); + return; + } + + InviteByEmailTask task = new InviteByEmailTask(email, progressLoadingInviteByEmail, new GenericTaskHandler() { + @Override + public void beforeStart() { + Helper.setViewEnabled(buttonInviteByEmail, false); + } + + @Override + public void onSuccess() { + Snackbar.make(getView(), getString(R.string.invite_sent_to, email), Snackbar.LENGTH_LONG).show(); + Helper.setViewText(inputEmail, null); + Helper.setViewEnabled(buttonInviteByEmail, true); + fetchInviteStatus(); + } + + @Override + public void onError(Exception error) { + showError(error.getMessage()); + Helper.setViewEnabled(buttonInviteByEmail, true); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + buttonGetStarted.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).simpleSignIn(); + } + } + }); + + channelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, long l) { + Object item = adapterView.getItemAtPosition(position); + if (item instanceof Claim) { + Claim claim = (Claim) item; + if (claim.isPlaceholder()) { + if (!fetchingChannels) { + showInlineChannelCreator(); + } + } else { + hideInlineChannelCreator(); + // build invite link + updateInviteLink(claim); + } + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + textInviteLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + copyInviteLink(); + } + }); + buttonCopyInviteLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + copyInviteLink(); + } + }); + + setupInlineChannelCreator( + inlineChannelCreator, + inlineChannelCreatorInputName, + inlineChannelCreatorInputDeposit, + inlineChannelCreatorInlineBalance, + inlineChannelCreatorInlineBalanceValue, + inlineChannelCreatorCancelLink, + inlineChannelCreatorCreateButton, + inlineChannelCreatorProgress + ); + } + + private void updateInviteLink(Claim claim) { + LbryUri canonical = LbryUri.tryParse(claim.getCanonicalUrl()); + String link = String.format(INVITE_LINK_FORMAT, + canonical != null ? String.format("@%s", canonical.getChannelName()) : claim.getName(), + canonical != null ? canonical.getChannelClaimId() : claim.getClaimId()); + textInviteLink.setText(link); + } + private void copyInviteLink() { + Context context = getContext(); + if (context != null && textInviteLink != null) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData data = ClipData.newPlainText("inviteLink", textInviteLink.getText()); + clipboard.setPrimaryClip(data); + } + Snackbar.make(getView(), R.string.invite_link_copied, Snackbar.LENGTH_SHORT).show(); + } + + private void updateChannelList(List channels) { + if (channelSpinnerAdapter == null) { + Context context = getContext(); + channelSpinnerAdapter = new InlineChannelSpinnerAdapter(context, R.layout.spinner_item_channel, new ArrayList<>(channels)); + channelSpinnerAdapter.addPlaceholder(false); + channelSpinnerAdapter.notifyDataSetChanged(); + } else { + channelSpinnerAdapter.clear(); + channelSpinnerAdapter.addAll(channels); + channelSpinnerAdapter.addPlaceholder(false); + channelSpinnerAdapter.notifyDataSetChanged(); + } + + if (channelSpinner != null) { + channelSpinner.setAdapter(channelSpinnerAdapter); + } + + if (channelSpinnerAdapter.getCount() > 1) { + channelSpinner.setSelection(1); + } + } + + public void onResume() { + super.onResume(); + layoutAccountDriver.setVisibility(Lbryio.isSignedIn() ? View.GONE : View.VISIBLE); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Invites", "Invites"); + } + + fetchInviteStatus(); + if (!Lbry.SDK_READY) { + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.addSdkStatusListener(this); + activity.addWalletBalanceListener(this); + } + } else { + onSdkReady(); + } + } + + public void onSdkReady() { + Helper.setViewVisibility(layoutSdkInitializing, View.GONE); + fetchChannels(); + } + + public void onStart() { + super.onStart(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.setWunderbarValue(null); + activity.hideFloatingWalletBalance(); + } + } + + public void clearInputFocus() { + inputEmail.clearFocus(); + inlineChannelCreatorInputName.clearFocus(); + inlineChannelCreatorInputDeposit.clearFocus(); + } + + public void onStop() { + clearInputFocus(); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.removeSdkStatusListener(this); + activity.removeWalletBalanceListener(this); + activity.showFloatingWalletBalance(); + } + super.onStop(); + } + + private void showInlineChannelCreator() { + Helper.setViewVisibility(inlineChannelCreator, View.VISIBLE); + } + private void hideInlineChannelCreator() { + Helper.setViewVisibility(inlineChannelCreator, View.GONE); + } + + private void fetchDefaultInviteLink() { + FetchReferralCodeTask task = new FetchReferralCodeTask(null, new FetchReferralCodeTask.FetchReferralCodeHandler() { + @Override + public void onSuccess(String referralCode) { + String previousLink = Helper.getValue(textInviteLink.getText()); + if (Helper.isNullOrEmpty(previousLink)) { + Helper.setViewText(textInviteLink, String.format("https://lbry.tv/$/invite/%s", referralCode)); + } + } + + @Override + public void onError(Exception error) { + // pass + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void disableChannelSpinner() { + Helper.setViewEnabled(channelSpinner, false); + hideInlineChannelCreator(); + } + private void enableChannelSpinner() { + Helper.setViewEnabled(channelSpinner, true); + Claim selectedClaim = (Claim) channelSpinner.getSelectedItem(); + if (selectedClaim != null) { + if (selectedClaim.isPlaceholder()) { + showInlineChannelCreator(); + } else { + hideInlineChannelCreator(); + } + } + } + + private void fetchChannels() { + if (Lbry.ownChannels != null && Lbry.ownChannels.size() > 0) { + updateChannelList(Lbry.ownChannels); + return; + } + + fetchingChannels = true; + disableChannelSpinner(); + ClaimListTask task = new ClaimListTask(Claim.TYPE_CHANNEL, progressLoadingChannels, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + Lbry.ownChannels = new ArrayList<>(claims); + updateChannelList(Lbry.ownChannels); + if (Lbry.ownChannels == null || Lbry.ownChannels.size() == 0) { + fetchDefaultInviteLink(); + } + enableChannelSpinner(); + fetchingChannels = false; + } + + @Override + public void onError(Exception error) { + fetchDefaultInviteLink(); + enableChannelSpinner(); + fetchingChannels = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void fetchInviteStatus() { + FetchInviteStatusTask task = new FetchInviteStatusTask(progressLoadingStatus, new FetchInviteStatusTask.FetchInviteStatusHandler() { + @Override + public void onSuccess(List invitees) { + if (inviteHistoryAdapter == null) { + inviteHistoryAdapter = new InviteeListAdapter(invitees, getContext()); + inviteHistoryAdapter.addHeader(); + } else { + inviteHistoryAdapter.addInvitees(invitees); + } + if (inviteHistoryList != null) { + inviteHistoryList.setAdapter(inviteHistoryAdapter); + } + Helper.setViewVisibility(inviteHistoryList, + inviteHistoryAdapter == null || inviteHistoryAdapter.getItemCount() < 2 ? View.GONE : View.VISIBLE + ); + } + + @Override + public void onError(Exception error) { + Helper.setViewVisibility(inviteHistoryList, + inviteHistoryAdapter == null || inviteHistoryAdapter.getItemCount() < 2 ? View.GONE : View.VISIBLE + ); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void setupInlineChannelCreator( + View container, + TextInputEditText inputChannelName, + TextInputEditText inputDeposit, + View inlineBalanceView, + TextView inlineBalanceValue, + View linkCancel, + MaterialButton buttonCreate, + View progressView) { + inputDeposit.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + Helper.setViewVisibility(inlineBalanceView, hasFocus ? View.VISIBLE : View.INVISIBLE); + } + }); + + linkCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Helper.setViewText(inputChannelName, null); + Helper.setViewText(inputDeposit, null); + Helper.setViewVisibility(container, View.GONE); + } + }); + + buttonCreate.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // validate deposit and channel name + String channelNameString = Helper.normalizeChannelName(Helper.getValue(inputChannelName.getText())); + Claim claimToSave = new Claim(); + claimToSave.setName(channelNameString); + String channelName = claimToSave.getName().startsWith("@") ? claimToSave.getName().substring(1) : claimToSave.getName(); + String depositString = Helper.getValue(inputDeposit.getText()); + if ("@".equals(channelName) || Helper.isNullOrEmpty(channelName)) { + showError(getString(R.string.please_enter_channel_name)); + return; + } + if (!LbryUri.isNameValid(channelName)) { + showError(getString(R.string.channel_name_invalid_characters)); + return; + } + if (Helper.channelExists(channelName)) { + showError(getString(R.string.channel_name_already_created)); + return; + } + + double depositAmount = 0; + try { + depositAmount = Double.valueOf(depositString); + } catch (NumberFormatException ex) { + // pass + showError(getString(R.string.please_enter_valid_deposit)); + return; + } + if (depositAmount == 0) { + String error = getResources().getQuantityString(R.plurals.min_deposit_required, depositAmount == 1 ? 1 : 2, String.valueOf(Helper.MIN_DEPOSIT)); + showError(error); + return; + } + if (Lbry.walletBalance == null || Lbry.walletBalance.getAvailable().doubleValue() < depositAmount) { + showError(getString(R.string.deposit_more_than_balance)); + return; + } + + ChannelCreateUpdateTask task = new ChannelCreateUpdateTask( + claimToSave, new BigDecimal(depositString), false, progressView, new ClaimResultHandler() { + @Override + public void beforeStart() { + Helper.setViewEnabled(inputChannelName, false); + Helper.setViewEnabled(inputDeposit, false); + Helper.setViewEnabled(buttonCreate, false); + Helper.setViewEnabled(linkCancel, false); + } + + @Override + public void onSuccess(Claim claimResult) { + if (!BuildConfig.DEBUG) { + LogPublishTask logPublishTask = new LogPublishTask(claimResult); + logPublishTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + // channel created + Bundle bundle = new Bundle(); + bundle.putString("claim_id", claimResult.getClaimId()); + bundle.putString("claim_name", claimResult.getName()); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_CHANNEL_CREATE, bundle); + + // add the claim to the channel list and set it as the selected item + channelSpinnerAdapter.add(claimResult); + channelSpinner.setSelection(channelSpinnerAdapter.getCount() - 1); + + Helper.setViewEnabled(inputChannelName, true); + Helper.setViewEnabled(inputDeposit, true); + Helper.setViewEnabled(buttonCreate, true); + Helper.setViewEnabled(linkCancel, true); + } + + @Override + public void onError(Exception error) { + Helper.setViewEnabled(inputChannelName, true); + Helper.setViewEnabled(inputDeposit, true); + Helper.setViewEnabled(buttonCreate, true); + Helper.setViewEnabled(linkCancel, true); + showError(error.getMessage()); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + Helper.setViewText(inlineBalanceValue, Helper.shortCurrencyFormat(Lbry.walletBalance.getAvailable().doubleValue())); + } + + @Override + public void onWalletBalanceUpdated(WalletBalance walletBalance) { + if (walletBalance != null && inlineChannelCreatorInlineBalanceValue != null) { + inlineChannelCreatorInlineBalanceValue.setText(Helper.shortCurrencyFormat(walletBalance.getAvailable().doubleValue())); + } + } + + private void showError(String message) { + Context context = getContext(); + if (context != null) { + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/wallet/RewardsFragment.java b/app/src/main/java/io/lbry/browser/ui/wallet/RewardsFragment.java new file mode 100644 index 00000000..eee6ebb0 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/wallet/RewardsFragment.java @@ -0,0 +1,298 @@ +package io.lbry.browser.ui.wallet; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.graphics.Typeface; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; + +import java.text.DecimalFormat; +import java.util.List; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.RewardListAdapter; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.model.lbryinc.Reward; +import io.lbry.browser.tasks.lbryinc.ClaimRewardTask; +import io.lbry.browser.tasks.lbryinc.FetchRewardsTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.Lbryio; + +public class RewardsFragment extends BaseFragment implements RewardListAdapter.RewardClickListener, SdkStatusListener { + + private boolean rewardClaimInProgress; + private View layoutAccountDriver; + private View layoutSdkInitializing; + private View linkNotInterested; + private TextView textAccountDriverTitle; + private TextView textFreeCreditsWorth; + private TextView textLearnMoreLink; + private MaterialButton buttonGetStarted; + + private ProgressBar rewardsLoading; + private RewardListAdapter adapter; + private RecyclerView rewardList; + private TextView linkFilterUnclaimed; + private TextView linkFilterAll; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_rewards, container, false); + + layoutAccountDriver = root.findViewById(R.id.rewards_account_driver_container); + layoutSdkInitializing = root.findViewById(R.id.container_sdk_initializing); + linkNotInterested = root.findViewById(R.id.rewards_not_interested_link); + textAccountDriverTitle = root.findViewById(R.id.rewards_account_driver_title); + textFreeCreditsWorth = root.findViewById(R.id.rewards_account_driver_credits_worth); + textLearnMoreLink = root.findViewById(R.id.rewards_account_driver_learn_more); + buttonGetStarted = root.findViewById(R.id.rewards_get_started_button); + + linkFilterUnclaimed = root.findViewById(R.id.rewards_filter_link_unclaimed); + linkFilterAll = root.findViewById(R.id.rewards_filter_link_all); + rewardList = root.findViewById(R.id.rewards_list); + rewardsLoading = root.findViewById(R.id.rewards_list_loading); + + Context context = getContext(); + LinearLayoutManager llm = new LinearLayoutManager(context); + rewardList.setLayoutManager(llm); + adapter = new RewardListAdapter(Lbryio.allRewards, context); + adapter.setClickListener(this); + adapter.setDisplayMode(RewardListAdapter.DISPLAY_MODE_UNCLAIMED); + rewardList.setAdapter(adapter); + + initUi(); + + return root; + } + + + public void onResume() { + super.onResume(); + checkRewardsStatus(); + fetchRewards(); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Rewards", "Rewards"); + } + + if (!Lbry.SDK_READY) { + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.addSdkStatusListener(this); + } + } else { + onSdkReady(); + } + } + + public void onSdkReady() { + Helper.setViewVisibility(layoutSdkInitializing, View.GONE); + } + + public void onStart() { + super.onStart(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.setWunderbarValue(null); + activity.hideFloatingWalletBalance(); + } + } + + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.removeSdkStatusListener(this); + activity.showFloatingWalletBalance(); + } + super.onStop(); + } + + private void fetchRewards() { + Helper.setViewVisibility(rewardList, View.INVISIBLE); + FetchRewardsTask task = new FetchRewardsTask(rewardsLoading, new FetchRewardsTask.FetchRewardsHandler() { + @Override + public void onSuccess(List rewards) { + Lbryio.updateRewardsLists(rewards); + updateUnclaimedRewardsValue(); + + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).showFloatingUnclaimedRewards(); + } + + if (adapter == null) { + adapter = new RewardListAdapter(rewards, getContext()); + adapter.setClickListener(RewardsFragment.this); + adapter.setDisplayMode(RewardListAdapter.DISPLAY_MODE_UNCLAIMED); + rewardList.setAdapter(adapter); + } else { + adapter.setRewards(rewards); + } + Helper.setViewVisibility(rewardList, View.VISIBLE); + } + + @Override + public void onError(Exception error) { + // pass + Helper.setViewVisibility(rewardList, View.VISIBLE); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void initUi() { + layoutSdkInitializing.setVisibility(Lbry.SDK_READY ? View.GONE : View.VISIBLE); + + linkNotInterested.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + + + Context context = getContext(); + + if (context instanceof MainActivity) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + sp.edit().putBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_REWARDS_NOT_INTERESTED, true).apply(); + + MainActivity activity = (MainActivity) context; + activity.hideFloatingRewardsValue(); + activity.onBackPressed(); + } + } + }); + buttonGetStarted.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).rewardsSignIn(); + } + } + }); + + linkFilterAll.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + linkFilterUnclaimed.setTypeface(null, Typeface.NORMAL); + linkFilterAll.setTypeface(null, Typeface.BOLD); + adapter.setDisplayMode(RewardListAdapter.DISPLAY_MODE_ALL); + if (adapter.getItemCount() == 1) { + fetchRewards(); + } + } + }); + linkFilterUnclaimed.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + linkFilterUnclaimed.setTypeface(null, Typeface.BOLD); + linkFilterAll.setTypeface(null, Typeface.NORMAL); + adapter.setDisplayMode(RewardListAdapter.DISPLAY_MODE_UNCLAIMED); + if (adapter.getItemCount() == 1) { + fetchRewards(); + } + } + }); + + updateUnclaimedRewardsValue(); + layoutAccountDriver.setVisibility(Lbryio.currentUser != null && Lbryio.currentUser.isRewardApproved() ? View.GONE : View.VISIBLE); + Helper.applyHtmlForTextView(textLearnMoreLink); + } + + private void checkRewardsStatus() { + Helper.setViewVisibility(layoutAccountDriver, Lbryio.currentUser != null && Lbryio.currentUser.isRewardApproved() ? View.GONE : View.VISIBLE); + } + + public void updateUnclaimedRewardsValue() { + try { + String accountDriverTitle = getResources().getQuantityString( + R.plurals.available_credits, + Lbryio.totalUnclaimedRewardAmount == 1 ? 1 : 2, + Helper.shortCurrencyFormat(Lbryio.totalUnclaimedRewardAmount)); + double unclaimedRewardAmountUsd = Lbryio.totalUnclaimedRewardAmount * Lbryio.LBCUSDRate; + Helper.setViewText(textAccountDriverTitle, accountDriverTitle); + Helper.setViewText(textFreeCreditsWorth, getString(R.string.free_credits_worth, Helper.SIMPLE_CURRENCY_FORMAT.format(unclaimedRewardAmountUsd))); + } catch (IllegalStateException ex) { + // pass + } + + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).updateRewardsUsdVale(); + } + } + + @Override + public void onRewardClicked(Reward reward, View loadingView) { + if (rewardClaimInProgress || reward.isCustom()) { + return; + } + claimReward(reward.getRewardType(), null, null, null, loadingView); + } + + @Override + public void onCustomClaimButtonClicked(String code, EditText inputCustomCode, MaterialButton buttonClaim, View loadingView) { + if (rewardClaimInProgress) { + return; + } + claimReward(Reward.TYPE_REWARD_CODE, code, inputCustomCode, buttonClaim, loadingView); + } + + private void claimReward(String type, String code, EditText inputClaimCode, MaterialButton buttonClaim, View loadingView) { + rewardClaimInProgress = true; + Helper.setViewEnabled(buttonClaim, false); + Helper.setViewEnabled(inputClaimCode, false); + ClaimRewardTask task = new ClaimRewardTask(type, code, loadingView, getContext(), new ClaimRewardTask.ClaimRewardHandler() { + @Override + public void onSuccess(double amountClaimed, String message) { + if (Helper.isNullOrEmpty(message)) { + message = getResources().getQuantityString( + R.plurals.claim_reward_message, + amountClaimed == 1 ? 1 : 2, + new DecimalFormat(Helper.LBC_CURRENCY_FORMAT_PATTERN).format(amountClaimed)); + } + View view = getView(); + if (view != null) { + Snackbar.make(view, message, Snackbar.LENGTH_LONG).show(); + } + Helper.setViewEnabled(buttonClaim, true); + Helper.setViewEnabled(inputClaimCode, true); + rewardClaimInProgress = false; + + fetchRewards(); + } + + @Override + public void onError(Exception error) { + Snackbar.make(getView(), error.getMessage(), Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + Helper.setViewEnabled(buttonClaim, true); + Helper.setViewEnabled(inputClaimCode, true); + rewardClaimInProgress = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/wallet/TransactionHistoryFragment.java b/app/src/main/java/io/lbry/browser/ui/wallet/TransactionHistoryFragment.java new file mode 100644 index 00000000..d9b70c7c --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/wallet/TransactionHistoryFragment.java @@ -0,0 +1,168 @@ +package io.lbry.browser.ui.wallet; + +import android.content.Context; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.TransactionListAdapter; +import io.lbry.browser.model.Transaction; +import io.lbry.browser.tasks.wallet.TransactionListTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.LbryUri; + +public class TransactionHistoryFragment extends BaseFragment implements TransactionListAdapter.TransactionClickListener { + + private static final int TRANSACTION_PAGE_LIMIT = 50; + private boolean transactionsHaveReachedEnd; + private boolean transactionsLoading; + private ProgressBar loading; + private RecyclerView transactionList; + private TransactionListAdapter adapter; + private View noTransactionsView; + private int currentTransactionPage; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_transaction_history, container, false); + + loading = root.findViewById(R.id.transaction_history_loading); + transactionList = root.findViewById(R.id.transaction_history_list); + noTransactionsView = root.findViewById(R.id.transaction_history_no_transactions); + + Context context = getContext(); + LinearLayoutManager llm = new LinearLayoutManager(context); + transactionList.setLayoutManager(llm); + transactionList.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL)); + + transactionList.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + if (transactionsLoading) { + return; + } + LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager(); + if (lm != null) { + int visibleItemCount = lm.getChildCount(); + int totalItemCount = lm.getItemCount(); + int pastVisibleItems = lm.findFirstVisibleItemPosition(); + if (pastVisibleItems + visibleItemCount >= totalItemCount) { + if (!transactionsHaveReachedEnd) { + // load more + currentTransactionPage++; + loadTransactions(); + } + } + } + } + }); + + return root; + } + + @Override + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Transaction History", "TransactionHistory"); + } + + if (adapter != null && adapter.getItemCount() > 0 && transactionList != null) { + transactionList.setAdapter(adapter); + } + loadTransactions(); + } + + private void checkNoTransactions() { + Helper.setViewVisibility(noTransactionsView, adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + + private void loadTransactions() { + currentTransactionPage = currentTransactionPage == 0 ? 1 : currentTransactionPage; + transactionsLoading = true; + TransactionListTask task = new TransactionListTask(currentTransactionPage, TRANSACTION_PAGE_LIMIT, loading, new TransactionListTask.TransactionListHandler() { + @Override + public void onSuccess(List transactions, boolean hasReachedEnd) { + transactionsLoading = false; + transactionsHaveReachedEnd = hasReachedEnd; + if (adapter == null) { + adapter = new TransactionListAdapter(transactions, getContext()); + adapter.setListener(TransactionHistoryFragment.this); + if (transactionList != null) { + transactionList.setAdapter(adapter); + } + } else { + adapter.addTransactions(transactions); + } + checkNoTransactions(); + } + + @Override + public void onError(Exception error) { + transactionsLoading = false; + checkNoTransactions(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + @Override + public void onStart() { + super.onStart(); + MainActivity activity = (MainActivity) getContext(); + if (activity != null) { + activity.hideSearchBar(); + activity.hideFloatingWalletBalance(); + activity.showNavigationBackIcon(); + activity.lockDrawer(); + + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(R.string.transaction_history); + } + } + } + + @Override + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.restoreToggle(); + activity.showFloatingWalletBalance(); + } + super.onStop(); + } + + public void onTransactionClicked(Transaction transaction) { + // Don't do anything? Or open the transaction in a browser? + } + public void onClaimUrlClicked(LbryUri uri) { + Context context = getContext(); + if (uri != null && context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (uri.isChannel()) { + activity.openChannelUrl(uri.toString()); + } else { + activity.openFileUrl(uri.toString()); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/wallet/WalletFragment.java b/app/src/main/java/io/lbry/browser/ui/wallet/WalletFragment.java new file mode 100644 index 00000000..7ae5ec9e --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/wallet/WalletFragment.java @@ -0,0 +1,537 @@ +package io.lbry.browser.ui.wallet; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.switchmaterial.SwitchMaterial; +import com.google.android.material.textfield.TextInputEditText; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.List; +import java.util.Locale; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.TransactionListAdapter; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.listener.WalletBalanceListener; +import io.lbry.browser.model.NavMenuItem; +import io.lbry.browser.model.Transaction; +import io.lbry.browser.model.WalletBalance; +import io.lbry.browser.tasks.wallet.TransactionListTask; +import io.lbry.browser.tasks.wallet.WalletAddressUnusedTask; +import io.lbry.browser.tasks.wallet.WalletSendTask; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.ui.publish.PublishFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; + +public class WalletFragment extends BaseFragment implements SdkStatusListener, WalletBalanceListener { + + private View layoutAccountRecommended; + private View layoutSdkInitializing; + private View linkSkipAccount; + private TextView textWalletBalance; + private TextView textWalletBalanceUSD; + private TextView textTipsBalance; + private TextView textTipsBalanceUSD; + private TextView textClaimsBalance; + private TextView textSupportsBalance; + private ProgressBar walletSendProgress; + + private View loadingRecentContainer; + private View inlineBalanceContainer; + private TextView textWalletInlineBalance; + private MaterialButton buttonSignUp; + private RecyclerView recentTransactionsList; + private View linkViewAll; + private TextView textConvertCredits; + private TextView textConvertCreditsBittrex; + private TextView textEarnMoreTips; + private TextView textWhatSyncMeans; + private TextView textWalletReceiveAddress; + private TextView textWalletHintSyncStatus; + private ImageButton buttonCopyReceiveAddress; + private MaterialButton buttonGetNewAddress; + private TextInputEditText inputSendAddress; + private TextInputEditText inputSendAmount; + private MaterialButton buttonSend; + private TextView textConnectedEmail; + private SwitchMaterial switchSyncStatus; + private TextView linkManualBackup; + private TextView linkSyncFAQ; + private TextView textNoRecentTransactions; + + private boolean hasFetchedRecentTransactions = false; + private TransactionListAdapter recentTransactionsAdapter; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_wallet, container, false); + + loadingRecentContainer = root.findViewById(R.id.wallet_loading_recent_container); + layoutAccountRecommended = root.findViewById(R.id.wallet_account_recommended_container); + layoutSdkInitializing = root.findViewById(R.id.container_sdk_initializing); + linkSkipAccount = root.findViewById(R.id.wallet_skip_account_link); + buttonSignUp = root.findViewById(R.id.wallet_sign_up_button); + + inlineBalanceContainer = root.findViewById(R.id.wallet_inline_balance_container); + textWalletInlineBalance = root.findViewById(R.id.wallet_inline_balance_value); + walletSendProgress = root.findViewById(R.id.wallet_send_progress); + + textWalletBalance = root.findViewById(R.id.wallet_balance_value); + textWalletBalanceUSD = root.findViewById(R.id.wallet_balance_usd_value); + textTipsBalance = root.findViewById(R.id.wallet_balance_tips); + textTipsBalanceUSD = root.findViewById(R.id.wallet_balance_tips_usd_value); + textClaimsBalance = root.findViewById(R.id.wallet_balance_staked_publishes); + textSupportsBalance = root.findViewById(R.id.wallet_balance_staked_supports); + textWalletHintSyncStatus = root.findViewById(R.id.wallet_hint_sync_status); + + recentTransactionsList = root.findViewById(R.id.wallet_recent_transactions_list); + linkViewAll = root.findViewById(R.id.wallet_link_view_all); + textNoRecentTransactions = root.findViewById(R.id.wallet_no_recent_transactions); + textConvertCredits = root.findViewById(R.id.wallet_hint_convert_credits); + textConvertCreditsBittrex = root.findViewById(R.id.wallet_hint_convert_credits_bittrex); + textEarnMoreTips = root.findViewById(R.id.wallet_hint_earn_more_tips); + textWhatSyncMeans = root.findViewById(R.id.wallet_hint_what_sync_means); + textWalletReceiveAddress = root.findViewById(R.id.wallet_receive_address); + buttonCopyReceiveAddress = root.findViewById(R.id.wallet_copy_receive_address); + buttonGetNewAddress = root.findViewById(R.id.wallet_get_new_address); + inputSendAddress = root.findViewById(R.id.wallet_input_send_address); + inputSendAmount = root.findViewById(R.id.wallet_input_amount); + buttonSend = root.findViewById(R.id.wallet_send); + textConnectedEmail = root.findViewById(R.id.wallet_connected_email); + switchSyncStatus = root.findViewById(R.id.wallet_switch_sync_status); + linkManualBackup = root.findViewById(R.id.wallet_link_manual_backup); + linkSyncFAQ = root.findViewById(R.id.wallet_link_sync_faq); + + initUi(); + + return root; + } + + private void copyReceiveAddress() { + Context context = getContext(); + if (context != null && textWalletReceiveAddress != null) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData data = ClipData.newPlainText("address", textWalletReceiveAddress.getText()); + clipboard.setPrimaryClip(data); + } + Snackbar.make(getView(), R.string.address_copied, Snackbar.LENGTH_SHORT).show(); + } + + private void fetchRecentTransactions() { + if (hasFetchedRecentTransactions) { + return; + } + + Helper.setViewVisibility(textNoRecentTransactions, View.GONE); + TransactionListTask task = new TransactionListTask(1, 5, loadingRecentContainer, new TransactionListTask.TransactionListHandler() { + @Override + public void onSuccess(List transactions, boolean hasReachedEnd) { + hasFetchedRecentTransactions = true; + recentTransactionsAdapter = new TransactionListAdapter(transactions, getContext()); + recentTransactionsAdapter.setListener(new TransactionListAdapter.TransactionClickListener() { + @Override + public void onTransactionClicked(Transaction transaction) { + + } + + @Override + public void onClaimUrlClicked(LbryUri uri) { + Context context = getContext(); + if (uri != null && context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + if (uri.isChannel()) { + activity.openChannelUrl(uri.toString()); + } else { + activity.openFileUrl(uri.toString()); + } + } + } + }); + recentTransactionsList.setAdapter(recentTransactionsAdapter); + displayNoRecentTransactions(); + } + + @Override + public void onError(Exception error) { + hasFetchedRecentTransactions = true; + displayNoRecentTransactions(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void displayNoRecentTransactions() { + boolean showNoTransactionsView = hasFetchedRecentTransactions && + (recentTransactionsAdapter == null || recentTransactionsAdapter.getItemCount() == 0); + Helper.setViewVisibility(textNoRecentTransactions, showNoTransactionsView ? View.VISIBLE : View.GONE); + } + + private boolean validateSend() { + String recipientAddress = Helper.getValue(inputSendAddress.getText()); + String amountString = Helper.getValue(inputSendAmount.getText()); + if (!recipientAddress.matches(LbryUri.REGEX_ADDRESS)) { + Snackbar.make(getView(), R.string.invalid_recipient_address, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + return false; + } + + if (!Helper.isNullOrEmpty(amountString)) { + try { + double amountValue = Double.valueOf(amountString); + double availableAmount = Lbry.walletBalance.getAvailable().doubleValue(); + if (availableAmount < amountValue) { + Snackbar.make(getView(), R.string.insufficient_balance, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + return false; + } + } catch (NumberFormatException ex) { + // pass + Snackbar.make(getView(), R.string.invalid_amount, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + return false; + } + } + + return true; + } + + @SuppressWarnings("ClickableViewAccessibility") + private void initUi() { + onWalletBalanceUpdated(Lbry.walletBalance); + + Helper.applyHtmlForTextView(textConvertCredits); + Helper.applyHtmlForTextView(textConvertCreditsBittrex); + Helper.applyHtmlForTextView(textWhatSyncMeans); + Helper.applyHtmlForTextView(linkManualBackup); + Helper.applyHtmlForTextView(linkSyncFAQ); + + Context context = getContext(); + LinearLayoutManager llm = new LinearLayoutManager(context); + recentTransactionsList.setLayoutManager(llm); + DividerItemDecoration itemDecoration = new DividerItemDecoration(context, DividerItemDecoration.VERTICAL); + itemDecoration.setDrawable(ContextCompat.getDrawable(context, R.drawable.thin_divider)); + recentTransactionsList.addItemDecoration(itemDecoration); + + textEarnMoreTips.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (context instanceof MainActivity) { + ((MainActivity) context).openFragment(PublishFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH); + } + } + }); + + buttonSignUp.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).walletSyncSignIn(); + } + } + }); + buttonGetNewAddress.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + generateNewAddress(); + } + }); + textWalletReceiveAddress.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + copyReceiveAddress(); + } + }); + buttonCopyReceiveAddress.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + copyReceiveAddress(); + } + }); + buttonSend.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (validateSend()) { + sendCredits(); + } + } + }); + + inputSendAddress.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + inputSendAddress.setHint(hasFocus ? getString(R.string.recipient_address_placeholder) : ""); + } + }); + inputSendAmount.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + inputSendAmount.setHint(hasFocus ? getString(R.string.zero) : ""); + inlineBalanceContainer.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); + } + }); + + layoutSdkInitializing.setVisibility(Lbry.SDK_READY ? View.GONE : View.VISIBLE); + layoutAccountRecommended.setVisibility(hasSkippedAccount() || Lbryio.isSignedIn() ? View.GONE : View.VISIBLE); + linkSkipAccount.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + sp.edit().putBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_SKIP_WALLET_ACCOUNT, true).apply(); + layoutAccountRecommended.setVisibility(View.GONE); + } + }); + + linkViewAll.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).openFragment(TransactionHistoryFragment.class, true, NavMenuItem.ID_ITEM_WALLET); + } + } + }); + + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + final boolean walletSyncEnabled = sp.getBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_WALLET_SYNC_ENABLED, false); + switchSyncStatus.setChecked(walletSyncEnabled); + switchSyncStatus.setText(walletSyncEnabled ? R.string.on : R.string.off); + textWalletHintSyncStatus.setText(walletSyncEnabled ? R.string.backup_synced : R.string.backup_notsynced); + textConnectedEmail.setText(walletSyncEnabled ? Lbryio.getSignedInEmail() : null); + GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if (switchSyncStatus.isChecked()) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + sp.edit().putBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_WALLET_SYNC_ENABLED, false).apply(); + switchSyncStatus.setText(R.string.off); + switchSyncStatus.setChecked(false); + } else { + // launch verification activity for wallet sync flow + Context context = getContext(); + if (context instanceof MainActivity) { + ((MainActivity) context).walletSyncSignIn(); + } + } + return true; + } + }; + GestureDetector detector = new GestureDetector(getContext(), gestureListener); + + switchSyncStatus.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + detector.onTouchEvent(motionEvent); + return true; + } + }); + } + + public void onWalletSyncEnabled() { + switchSyncStatus.setText(R.string.on); + switchSyncStatus.setChecked(true); + textWalletHintSyncStatus.setText(R.string.backup_synced); + textConnectedEmail.setText(Lbryio.getSignedInEmail()); + fetchRecentTransactions(); + } + + private void disableSendControls() { + inputSendAddress.clearFocus(); + inputSendAmount.clearFocus(); + Helper.setViewEnabled(buttonSend, false); + Helper.setViewEnabled(inputSendAddress, false); + Helper.setViewEnabled(inputSendAmount, false); + } + + private void enableSendControls() { + Helper.setViewEnabled(buttonSend, true); + Helper.setViewEnabled(inputSendAddress, true); + Helper.setViewEnabled(inputSendAmount, true); + } + + private void sendCredits() { + // wallet_send task + String recipientAddress = Helper.getValue(inputSendAddress.getText()); + String amountString = Helper.getValue(inputSendAmount.getText()); + String amount = new DecimalFormat(Helper.SDK_AMOUNT_FORMAT, new DecimalFormatSymbols(Locale.US)). + format(new BigDecimal(amountString).doubleValue()); + + disableSendControls(); + WalletSendTask task = new WalletSendTask(recipientAddress, amount, walletSendProgress, new WalletSendTask.WalletSendHandler() { + @Override + public void onSuccess() { + double sentAmount = Double.valueOf(amount); + String message = getResources().getQuantityString( + R.plurals.you_sent_credits, sentAmount == 1.0 ? 1 : 2, + new DecimalFormat("#,###.##").format(sentAmount)); + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show(); + inputSendAddress.setText(null); + inputSendAmount.setText(null); + enableSendControls(); + } + + @Override + public void onError(Exception error) { + Snackbar.make(getView(), R.string.send_credit_error, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED).setTextColor(Color.WHITE).show(); + enableSendControls(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void checkReceiveAddress() { + Context context = getContext(); + String receiveAddress = null; + if (context != null) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + receiveAddress = sp.getString(MainActivity.PREFERENCE_KEY_INTERNAL_WALLET_RECEIVE_ADDRESS, null); + } + if (Helper.isNullOrEmpty(receiveAddress)) { + if (Lbry.SDK_READY) { + generateNewAddress(); + } + } else if (textWalletReceiveAddress != null) { + textWalletReceiveAddress.setText(receiveAddress); + } + } + + private boolean hasSkippedAccount() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + return sp.getBoolean(MainActivity.PREFERENCE_KEY_INTERNAL_SKIP_WALLET_ACCOUNT, false); + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + Helper.setWunderbarValue(null, context); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Wallet", "Wallet"); + } + + Helper.setViewVisibility(layoutAccountRecommended, hasSkippedAccount() || Lbryio.isSignedIn() ? View.GONE : View.VISIBLE); + if (!Lbry.SDK_READY) { + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.addSdkStatusListener(this); + } + + checkReceiveAddress(); + } else { + onSdkReady(); + } + } + + public void onPause() { + hasFetchedRecentTransactions = false; + super.onPause(); + } + public void onStart() { + super.onStart(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.setWunderbarValue(null); + activity.hideFloatingWalletBalance(); + } + } + + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.removeWalletBalanceListener(this); + activity.showFloatingWalletBalance(); + } + super.onStop(); + } + + public void onSdkReady() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + activity.syncWalletAndLoadPreferences(); + activity.addWalletBalanceListener(this); + } + + // update view + if (layoutSdkInitializing != null) { + layoutSdkInitializing.setVisibility(View.GONE); + } + + checkReceiveAddress(); + fetchRecentTransactions(); + } + + public void generateNewAddress() { + WalletAddressUnusedTask task = new WalletAddressUnusedTask(new WalletAddressUnusedTask.WalletAddressUnusedHandler() { + @Override + public void beforeStart() { + Helper.setViewEnabled(buttonGetNewAddress, false); + } + @Override + public void onSuccess(String newAddress) { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + sp.edit().putString(MainActivity.PREFERENCE_KEY_INTERNAL_WALLET_RECEIVE_ADDRESS, newAddress).apply(); + Helper.setViewText(textWalletReceiveAddress, newAddress); + Helper.setViewEnabled(buttonGetNewAddress, true); + } + + @Override + public void onError(Exception error) { + Helper.setViewEnabled(buttonGetNewAddress, true); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public void onWalletBalanceUpdated(WalletBalance walletBalance) { + double balance = walletBalance.getAvailable().doubleValue(); + double usdBalance = balance * Lbryio.LBCUSDRate; + double tipsBalance = walletBalance.getTips().doubleValue(); + double tipsUsdBalance = tipsBalance * Lbryio.LBCUSDRate; + + String formattedBalance = Helper.SIMPLE_CURRENCY_FORMAT.format(balance); + Helper.setViewText(textWalletBalance, balance > 0 && formattedBalance.equals("0") ? Helper.FULL_LBC_CURRENCY_FORMAT.format(balance) : formattedBalance); + Helper.setViewText(textTipsBalance, Helper.shortCurrencyFormat(tipsBalance)); + Helper.setViewText(textClaimsBalance, Helper.shortCurrencyFormat(walletBalance.getClaims().doubleValue())); + Helper.setViewText(textSupportsBalance, Helper.shortCurrencyFormat(walletBalance.getSupports().doubleValue())); + Helper.setViewText(textWalletInlineBalance, Helper.shortCurrencyFormat(balance)); + if (Lbryio.LBCUSDRate > 0) { + // only update display usd values if the rate is loaded + Helper.setViewText(textWalletBalanceUSD, String.format("≈$%s", Helper.SIMPLE_CURRENCY_FORMAT.format(usdBalance))); + Helper.setViewText(textTipsBalanceUSD, String.format("≈$%s", Helper.SIMPLE_CURRENCY_FORMAT.format(tipsUsdBalance))); + } + } +} diff --git a/app/src/main/java/io/lbry/browser/utils/Events.java b/app/src/main/java/io/lbry/browser/utils/Events.java new file mode 100644 index 00000000..3b77d1e2 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/utils/Events.java @@ -0,0 +1,5 @@ +package io.lbry.browser.utils; + +public class Events { + public static final String FIRST_RUN_COMPLETED = "first_run_completed"; +} diff --git a/app/src/main/java/io/lbry/browser/utils/ExoplayerAudioRenderer.java b/app/src/main/java/io/lbry/browser/utils/ExoplayerAudioRenderer.java new file mode 100644 index 00000000..832db7f2 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/utils/ExoplayerAudioRenderer.java @@ -0,0 +1,53 @@ +package io.lbry.browser.utils; + +import android.content.Context; +import android.os.Handler; + +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.DefaultRenderersFactory; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.audio.AudioProcessor; +import com.google.android.exoplayer2.audio.AudioRendererEventListener; +import com.google.android.exoplayer2.audio.TeeAudioProcessor; +import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; +import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; + +import java.util.ArrayList; + +public class ExoplayerAudioRenderer extends DefaultRenderersFactory { + + private TeeAudioProcessor.AudioBufferSink audioBufferSink; + + public ExoplayerAudioRenderer(Context context, TeeAudioProcessor.AudioBufferSink audioBufferSink) { + super(context); + this.audioBufferSink = audioBufferSink; + } + + @Override + protected void buildAudioRenderers( + Context context, + int extensionRendererMode, + MediaCodecSelector mediaCodecSelector, + @Nullable DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys, + boolean enableDecoderFallback, + AudioProcessor[] audioProcessors, + Handler eventHandler, + AudioRendererEventListener eventListener, + ArrayList out) { + AudioProcessor[] audioProcessorList = { new TeeAudioProcessor(audioBufferSink) }; + super.buildAudioRenderers( + context, + extensionRendererMode, + mediaCodecSelector, + drmSessionManager, + playClearSamplesWithoutKeys, + enableDecoderFallback, + audioProcessorList, + eventHandler, + eventListener, + out); + } +} diff --git a/app/src/main/java/io/lbry/browser/utils/Helper.java b/app/src/main/java/io/lbry/browser/utils/Helper.java new file mode 100644 index 00000000..108d287c --- /dev/null +++ b/app/src/main/java/io/lbry/browser/utils/Helper.java @@ -0,0 +1,756 @@ +package io.lbry.browser.utils; + +import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.text.method.LinkMovementMethod; +import android.view.ContextMenu; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.core.content.ContextCompat; +import androidx.core.text.HtmlCompat; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.dialog.ContentFromDialogFragment; +import io.lbry.browser.dialog.ContentSortDialogFragment; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.model.Tag; +import io.lbry.browser.model.UrlSuggestion; +import io.lbry.browser.model.ViewHistory; +import io.lbry.browser.tasks.localdata.SaveUrlHistoryTask; +import io.lbry.browser.tasks.localdata.SaveViewHistoryTask; +import okhttp3.MediaType; + +public final class Helper { + public static final String UNKNOWN = "Unknown"; + public static final String METHOD_GET = "GET"; + public static final String METHOD_POST = "POST"; + public static final String ISO_DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS"; + public static final String SDK_AMOUNT_FORMAT = "0.0#######"; + public static final MediaType FORM_MEDIA_TYPE = MediaType.parse("application/x-www-form-urlencoded"); + public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8"); + public static final int CONTENT_PAGE_SIZE = 25; + public static final double MIN_DEPOSIT = 0.001; + public static final String LBC_CURRENCY_FORMAT_PATTERN = "#,###.##"; + public static final String FILE_SIZE_FORMAT_PATTERN = "#,###.#"; + public static final DecimalFormat LBC_CURRENCY_FORMAT = new DecimalFormat(LBC_CURRENCY_FORMAT_PATTERN); + public static final DecimalFormat FULL_LBC_CURRENCY_FORMAT = new DecimalFormat("#,###.########"); + public static final DecimalFormat SIMPLE_CURRENCY_FORMAT = new DecimalFormat("#,##0.00"); + public static final SimpleDateFormat FILESTAMP_FORMAT = new SimpleDateFormat("yyyyMMdd_HHmmss"); + public static final String EXPLORER_TX_PREFIX = "https://explorer.lbry.com/tx"; + + public static final List PLAYBACK_SPEEDS = Arrays.asList(0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0); + + public static boolean isNull(String value) { + return value == null; + } + public static boolean isNullOrEmpty(String value) { + return value == null || value.trim().length() == 0; + } + + public static void buildPlaybackSpeedMenu(ContextMenu menu) { + int order = 0; + DecimalFormat formatter = new DecimalFormat("0.##"); + for (Double speed : PLAYBACK_SPEEDS) { + menu.add(0, Double.valueOf(speed * 100).intValue(), ++order, String.format("%sx", formatter.format(speed))); + } + } + + public static String capitalize(String value) { + StringBuilder sb = new StringBuilder(); + boolean capitalizeNext = true; + for (char c : value.toCharArray()) { + if (c == ' ') { + capitalizeNext = true; + sb.append(c); + } else { + if (capitalizeNext) { + sb.append(String.valueOf(c).toUpperCase()); + } else { + sb.append(c); + } + capitalizeNext = false; + } + } + return sb.toString(); + } + + public static String join(List list, String delimiter) { + StringBuilder sb = new StringBuilder(); + String delim = ""; + for (String s : list) { + sb.append(delim).append(s); + delim = delimiter; + } + return sb.toString(); + } + + public static JSONArray jsonArrayFromList(List values) { + JSONArray array = new JSONArray(); + for (T value : values) { + array.put(value); + } + return array; + } + + public static void unregisterReceiver(BroadcastReceiver receiver, Context context) { + if (receiver != null && context != null) { + context.unregisterReceiver(receiver); + } + } + + public static void setViewVisibility(View view, int visibility) { + if (view != null) { + view.setVisibility(visibility); + } + } + + public static void setViewProgress(ProgressBar progressBar, int progress) { + if (progressBar != null) { + progressBar.setProgress(progress); + } + } + + public static void setViewText(TextView view, int stringResourceId) { + if (view != null) { + view.setText(stringResourceId); + } + } + + public static void setViewText(TextView view, String text) { + if (view != null) { + view.setText(text); + } + } + + public static void closeCursor(Cursor cursor) { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + public static void closeDatabase(SQLiteDatabase db) { + if (db != null) { + db.close(); + } + } + + public static void closeCloseable(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException ex) { + // pass + } + } + + public static int parseInt(Object value, int defaultValue) { + try { + return Integer.parseInt(String.valueOf(value), 10); + } catch (NumberFormatException ex) { + return defaultValue; + } + } + + public static Double parseDouble(Object value, double defaultValue) { + try { + return Double.parseDouble(String.valueOf(value)); + } catch (NumberFormatException ex) { + return defaultValue; + } + } + + public static String formatDuration(long duration) { + long seconds = duration; + long hours = Double.valueOf(Math.floor(seconds / 3600.0)).longValue(); + seconds = duration - hours * 3600; + long minutes = Double.valueOf(Math.floor(seconds / 60.0)).longValue(); + seconds = seconds % 60; + + if (hours > 0) { + return String.format("%d:%02d:%02d", hours, minutes, seconds); + } + return String.format("%d:%02d", minutes, seconds); + } + public static String[] formatBytesParts(long bytes, boolean showTB) { + DecimalFormat formatter = new DecimalFormat(FILE_SIZE_FORMAT_PATTERN); + if (bytes < 1048576) { + // less than 1MB + return new String[] { formatter.format(bytes / 1024.0), "KB" }; + } + if (bytes < 1073741824) { + // less than 1GB + return new String[] { formatter.format(bytes / (1024.0 * 1024.0)), "MB" }; + } + if (showTB) { + if (bytes < (1073741824L * 1024L)) { + return new String[] { formatter.format(bytes / (1024.0 * 1024.0 * 1024.0)), "GB" }; + } + return new String[] { formatter.format(bytes / (1024.0 * 1024.0 * 1024.0 * 1024.0)), "TB" }; + } + return new String[] { formatter.format(bytes / (1024.0 * 1024.0 * 1024.0)), "GB" }; + } + + public static String formatBytes(long bytes, boolean showTB) { + DecimalFormat formatter = new DecimalFormat(FILE_SIZE_FORMAT_PATTERN); + if (bytes < 1048576) { + // less than 1MB + return String.format("%sKB", formatter.format(bytes / 1024.0)); + } + if (bytes < 1073741824) { + // less than 1GB + return String.format("%sMB", formatter.format(bytes / (1024.0 * 1024.0))); + } + if (showTB) { + if (bytes < (1073741824L * 1024L)) { + return String.format("%sGB", formatter.format(bytes / (1024.0 * 1024.0 * 1024.0))); + } + return String.format("%sTB", formatter.format(bytes / (1024.0 * 1024.0 * 1024.0 * 1024.0))); + } + return String.format("%sGB", formatter.format(bytes / (1024.0 * 1024.0 * 1024.0))); + } + + public static JSONObject getJSONObject(String name, JSONObject object) { + try { + return object.has(name) && !object.isNull(name) ? object.getJSONObject(name) : null; + } catch (JSONException ex) { + return null; + } + } + public static boolean getJSONBoolean(String name, boolean defaultValue, JSONObject object) { + try { + return object.has(name) && !object.isNull(name) ? object.getBoolean(name) : defaultValue; + } catch (JSONException ex) { + return defaultValue; + } + } + + public static String getJSONString(String name, String defaultValue, JSONObject object) { + try { + return object.has(name) && !object.isNull(name) ? object.getString(name) : defaultValue; + } catch (JSONException ex) { + return defaultValue; + } + } + + public static double getJSONDouble(String name, double defaultValue, JSONObject object) { + try { + return object.has(name) && !object.isNull(name) ? object.getDouble(name) : defaultValue; + } catch (JSONException ex) { + return defaultValue; + } + } + + + public static long getJSONLong(String name, long defaultValue, JSONObject object) { + try { + return object.has(name) && !object.isNull(name) ? object.getLong(name) : defaultValue; + } catch (JSONException ex) { + return defaultValue; + } + } + public static int getJSONInt(String name, int defaultValue, JSONObject object) { + try { + return object.has(name) && !object.isNull(name) ? object.getInt(name) : defaultValue; + } catch (JSONException ex) { + return defaultValue; + } + } + public static void setViewEnabled(View view, boolean enabled) { + if (view != null) { + view.setEnabled(enabled); + } + } + + public static String shortCurrencyFormat(double value) { + DecimalFormat format = new DecimalFormat("#,###.#"); + if (value > 1000000000.00) { + return String.format("%sB", format.format(value / 1000000000.0)); + } + if (value > 1000000.0) { + return String.format("%sM",format.format( value / 1000000.0)); + } + if (value > 1000.0) { + return String.format("%sK", format.format(value / 1000.0)); + } + + if (value == 0) { + return "0"; + } + + return format.format(value).equals("0") ? FULL_LBC_CURRENCY_FORMAT.format(value) : format.format(value); + } + + public static String getValue(CharSequence cs) { + return cs != null ? cs.toString() : ""; + } + + public static List buildContentSortOrder(int sortBy) { + List sortOrder = new ArrayList<>(); + switch (sortBy) { + case ContentSortDialogFragment.ITEM_SORT_BY_NEW: + sortOrder = Arrays.asList(Claim.ORDER_BY_RELEASE_TIME); break; + case ContentSortDialogFragment.ITEM_SORT_BY_TOP: + sortOrder = Arrays.asList(Claim.ORDER_BY_EFFECTIVE_AMOUNT); break; + case ContentSortDialogFragment.ITEM_SORT_BY_TRENDING: + sortOrder = Arrays.asList(Claim.ORDER_BY_TRENDING_GROUP, Claim.ORDER_BY_TRENDING_MIXED); break; + } + + return sortOrder; + } + + public static String buildReleaseTime(int contentFrom) { + if (contentFrom == 0 || contentFrom == ContentFromDialogFragment.ITEM_FROM_ALL_TIME) { + return null; + } + + Calendar cal = Calendar.getInstance(); + switch (contentFrom) { + case ContentFromDialogFragment.ITEM_FROM_PAST_24_HOURS: cal.add(Calendar.HOUR_OF_DAY, -24) ; break; + case ContentFromDialogFragment.ITEM_FROM_PAST_WEEK: default: cal.add(Calendar.DAY_OF_YEAR, -7); break; + case ContentFromDialogFragment.ITEM_FROM_PAST_MONTH: cal.add(Calendar.MONTH, -1); break; + case ContentFromDialogFragment.ITEM_FROM_PAST_YEAR: cal.add(Calendar.YEAR, -1); break; + } + + return String.format(">%d", Double.valueOf(cal.getTimeInMillis() / 1000.0).longValue()); + } + + public static final Map randomColorMap = new HashMap<>(); + public static int generateRandomColorForValue(String value) { + if (Helper.isNullOrEmpty(value)) { + return 0; + } + + if (randomColorMap.containsKey(value)) { + return randomColorMap.get(value); + } + + Random random = new Random(value.hashCode()); + int color = Color.argb(255, random.nextInt(256), random.nextInt(256), random.nextInt(256)); + randomColorMap.put(value, color); + return color; + } + + public static void setIconViewBackgroundColor(View view, int color, boolean isPlaceholder, Context context) { + Drawable bg = view.getBackground(); + if (bg instanceof ShapeDrawable) { + ((ShapeDrawable) bg).getPaint().setColor(isPlaceholder ? ContextCompat.getColor(context, android.R.color.transparent) : color); + } else if (bg instanceof GradientDrawable) { + ((GradientDrawable) bg).setColor(isPlaceholder ? ContextCompat.getColor(context, android.R.color.transparent) : color); + } else if (bg instanceof ColorDrawable) { + ((ColorDrawable) bg).setColor(isPlaceholder ? ContextCompat.getColor(context, android.R.color.transparent) : color); + } + } + + public static List getTagObjectsForTags(List tags) { + List tagObjects = new ArrayList<>(tags.size()); + for (String tag : tags) { + tagObjects.add(new Tag(tag)); + } + return tagObjects; + } + public static List getTagsForTagObjects(List tagObjects) { + List tags = new ArrayList<>(tagObjects.size()); + for (Tag tagObject : tagObjects) { + tags.add(tagObject.getLowercaseName()); + } + return tags; + } + public static List mergeKnownTags(List fetchedTags) { + List allKnownTags = getTagObjectsForTags(Predefined.DEFAULT_KNOWN_TAGS); + List followIndexes = new ArrayList<>(); + for (Tag tag : fetchedTags) { + if (!allKnownTags.contains(tag)) { + allKnownTags.add(tag); + } else if (tag.isFollowed()) { + followIndexes.add(allKnownTags.indexOf(tag)); + } + } + for (int index : followIndexes) { + allKnownTags.get(index).setFollowed(true); + } + return allKnownTags; + } + public static List filterFollowedTags(List tags) { + List followedTags = new ArrayList<>(); + for (Tag tag : tags) { + if (tag.isFollowed()) { + followedTags.add(tag); + } + } + return followedTags; + } + public static List filterDeletedClaims(List claims) { + List filtered = new ArrayList<>(); + for (Claim claim : claims) { + if (!Lbry.abandonedClaimIds.contains(claim.getClaimId())) { + filtered.add(claim); + } + } + return filtered; + } + + public static void setWunderbarValue(String value, Context context) { + if (context instanceof MainActivity) { + ((MainActivity) context).setWunderbarValue(value); + } + } + + public static String getDeviceName() { + if (Helper.isNullOrEmpty(Build.MANUFACTURER) || UNKNOWN.equalsIgnoreCase(Build.MANUFACTURER)) { + return Build.MODEL; + } + return String.format("%s %s", Build.MANUFACTURER, Build.MODEL); + } + + public static boolean channelExists(String channelName) { + for (Claim claim : Lbry.ownChannels) { + if (channelName.equalsIgnoreCase(claim.getName())) { + return true; + } + } + return false; + } + public static boolean claimNameExists(String claimName) { + for (Claim claim : Lbry.ownClaims) { + if (claimName.equalsIgnoreCase(claim.getName())) { + return true; + } + } + return false; + } + + public static String getRealPathFromURI_API19(final Context context, final Uri uri) { + return getRealPathFromURI_API19(context, uri, false); + } + /** + * https://gist.github.com/HBiSoft/15899990b8cd0723c3a894c1636550a8 + */ + @SuppressLint("NewApi") + public static String getRealPathFromURI_API19(final Context context, final Uri uri, boolean folderPath) { + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + // This is for checking Main Memory + if ("primary".equalsIgnoreCase(type)) { + if (split.length > 1) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } else { + return Environment.getExternalStorageDirectory() + "/"; + } + // This is for checking SD Card + } else { + return "storage" + "/" + docId.replace(":", "/"); + } + + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + String fileName = getFilePath(context, uri); + if (fileName != null) { + String extStorageDirectory = Environment.getExternalStorageDirectory().toString(); + return folderPath ? + String.format("%s/Download", extStorageDirectory) : + String.format("%s/Download/%s", extStorageDirectory, fileName); + } + + String id = DocumentsContract.getDocumentId(uri); + if (id.startsWith("raw:")) { + id = id.replaceFirst("raw:", ""); + File file = new File(id); + if (file.exists()) + return id; + } + + String[] contentUriPrefixesToTry = new String[]{ + "content://downloads/public_downloads", + "content://downloads/my_downloads", + "content://downloads/all_downloads" + }; + + for (String contentUriPrefix : contentUriPrefixesToTry) { + Uri contentUri = Helper.parseInt(id, -1) > 0 ? + ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id)) : + Uri.parse(contentUriPrefix); + try { + String path = getDataColumn(context, contentUri, null, null); + if (path != null) { + return path; + } + } catch (Exception ex) { + // pass + } + } + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + public static String getFilePath(Context context, Uri uri) { + Cursor cursor = null; + final String[] projection = { MediaStore.MediaColumns.DISPLAY_NAME }; + + try { + cursor = context.getContentResolver().query(uri, projection, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME); + return cursor.getString(index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + public static void applyHtmlForTextView(TextView textView) { + textView.setMovementMethod(LinkMovementMethod.getInstance()); + textView.setText(HtmlCompat.fromHtml(textView.getText().toString(), HtmlCompat.FROM_HTML_MODE_LEGACY)); + } + + public static List filterInvalidReposts(List claims) { + List filtered = new ArrayList<>(); + for (Claim claim : claims) { + if (Claim.TYPE_REPOST.equalsIgnoreCase(claim.getValueType()) && claim.getRepostedClaim() == null) { + continue; + } + filtered.add(claim); + } + return filtered; + } + + public static List filterDownloads(List files) { + List filtered = new ArrayList<>(); + for (int i = 0; i < files.size(); i++) { + LbryFile file = files.get(i); + // remove own claims as well + if (file.getClaim() != null && Lbry.ownClaims.contains(file.getClaim())) { + continue; + } + if (!Helper.isNullOrEmpty(file.getDownloadPath())) { + filtered.add(file); + } + } + return filtered; + } + + public static List claimsFromFiles(List files) { + List claims = new ArrayList<>(); + for (LbryFile file : files) { + claims.add(file.getClaim()); + } + return claims; + } + + public static List claimsFromViewHistory(List history) { + List claims = new ArrayList<>(); + for (ViewHistory item : history) { + claims.add(Claim.fromViewHistory(item)); + } + return claims; + } + + public static void saveUrlHistory(String url, String title, int type) { + DatabaseHelper dbHelper = DatabaseHelper.getInstance(); + if (dbHelper != null) { + UrlSuggestion suggestion = new UrlSuggestion(); + suggestion.setUri(LbryUri.tryParse(url)); + suggestion.setType(type); + suggestion.setText(Helper.isNull(title) ? "" : title); + new SaveUrlHistoryTask(suggestion, dbHelper, null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + public static void saveViewHistory(String url, Claim claim) { + DatabaseHelper dbHelper = DatabaseHelper.getInstance(); + if (dbHelper != null) { + ViewHistory viewHistory = ViewHistory.fromClaimWithUrlAndDeviceName(claim, url, getDeviceName()); + new SaveViewHistoryTask(viewHistory, dbHelper, null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + public static String normalizeChannelName(String channelName) { + if (!channelName.startsWith("@")) { + return String.format("@%s", channelName); + } + return channelName; + } + + public static int getScaledValue(int value, float scale) { + return (int) (value * scale + 0.5f); + } + + public static String generateUrl() { + Random random = new Random(); + StringBuilder sb = new StringBuilder(); + sb.append(Predefined.ADJECTIVES.get(random.nextInt(Predefined.ADJECTIVES.size()))).append("-"). + append(Predefined.ADJECTIVES.get(random.nextInt(Predefined.ADJECTIVES.size()))).append("-"). + append(Predefined.ANIMALS.get(random.nextInt(Predefined.ANIMALS.size()))); + return sb.toString().toLowerCase(); + } + + public static void refreshRecyclerView(RecyclerView rv) { + if (rv == null) { + return; + } + + RecyclerView.Adapter adapter = rv.getAdapter(); + int prevScrollPosition = 0; + + RecyclerView.LayoutManager lm = rv.getLayoutManager(); + if (lm instanceof LinearLayoutManager) { + prevScrollPosition = ((LinearLayoutManager) lm).findLastCompletelyVisibleItemPosition(); + } else if (lm instanceof GridLayoutManager) { + prevScrollPosition = ((GridLayoutManager) lm).findLastCompletelyVisibleItemPosition(); + } + + rv.setAdapter(null); + rv.setAdapter(adapter); + rv.scrollToPosition(prevScrollPosition > 0 ? prevScrollPosition : 0); + } + + public static String makeid(int length) { + Random random = new Random(); + String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + StringBuilder id = new StringBuilder(); + for (int i = 0; i < length; i++) { + id.append(chars.charAt(random.nextInt(chars.length()))); + } + return id.toString(); + } +} diff --git a/app/src/main/java/io/lbry/browser/utils/Lbry.java b/app/src/main/java/io/lbry/browser/utils/Lbry.java new file mode 100644 index 00000000..ec2ed59d --- /dev/null +++ b/app/src/main/java/io/lbry/browser/utils/Lbry.java @@ -0,0 +1,493 @@ +package io.lbry.browser.utils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.exceptions.LbryRequestException; +import io.lbry.browser.exceptions.LbryResponseException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.ClaimCacheKey; +import io.lbry.browser.model.ClaimSearchCacheValue; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.model.Tag; +import io.lbry.browser.model.Transaction; +import io.lbry.browser.model.WalletBalance; +import io.lbry.lbrysdk.Utils; +import kotlin.Pair; +import okhttp3.CacheControl; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public final class Lbry { + private static final Object lock = new Object(); + public static LinkedHashMap claimCache = new LinkedHashMap<>(); + public static LinkedHashMap, ClaimSearchCacheValue> claimSearchCache = new LinkedHashMap<>(); + public static WalletBalance walletBalance = new WalletBalance(); + public static List knownTags = new ArrayList<>(); + public static List followedTags = new ArrayList<>(); + public static List ownClaims = new ArrayList<>(); + public static List ownChannels = new ArrayList<>(); // Make this a subset of ownClaims? + public static List abandonedClaimIds = new ArrayList<>(); + + public static final int TTL_CLAIM_SEARCH_VALUE = 120000; // 2-minute TTL for cache + public static final String SDK_CONNECTION_STRING = "http://127.0.0.1:5279"; + public static final String LBRY_TV_CONNECTION_STRING = "https://api.lbry.tv/api/v1/proxy"; + public static final String TAG = "Lbry"; + + // Values to obtain from LBRY SDK status + public static boolean IS_STATUS_PARSED = false; // Check if the status has been parsed at least once + public static final String PLATFORM = String.format("Android %s (API %d)", Utils.getAndroidRelease(), Utils.getAndroidSdk()); + public static final String OS = "android"; + public static String INSTALLATION_ID = null; + public static String NODE_ID = null; + public static String DAEMON_VERSION = null; + + // JSON RPC API Call methods + public static final String METHOD_RESOLVE = "resolve"; + public static final String METHOD_CLAIM_SEARCH = "claim_search"; + public static final String METHOD_FILE_LIST = "file_list"; + public static final String METHOD_FILE_DELETE = "file_delete"; + public static final String METHOD_GET = "get"; + public static final String METHOD_PUBLISH = "publish"; + + public static final String METHOD_WALLET_BALANCE = "wallet_balance"; + public static final String METHOD_WALLET_ENCRYPT = "wallet_encrypt"; + public static final String METHOD_WALLET_DECRYPT = "wallet_decrypt"; + public static final String METHOD_VERSION = "version"; + + public static final String METHOD_WALLET_LIST = "wallet_list"; + public static final String METHOD_WALLET_SEND = "wallet_send"; + public static final String METHOD_WALLET_STATUS = "wallet_status"; + public static final String METHOD_WALLET_UNLOCK = "wallet_unlock"; + public static final String METHOD_ADDRESS_IS_MINE = "address_is_mine"; + public static final String METHOD_ADDRESS_UNUSED = "address_unused"; + public static final String METHOD_ADDRESS_LIST = "address_list"; + public static final String METHOD_TRANSACTION_LIST = "transaction_list"; + public static final String METHOD_UTXO_RELEASE = "utxo_release"; + public static final String METHOD_SUPPORT_CREATE = "support_create"; + public static final String METHOD_SUPPORT_ABANDON = "support_abandon"; + public static final String METHOD_SYNC_HASH = "sync_hash"; + public static final String METHOD_SYNC_APPLY = "sync_apply"; + public static final String METHOD_PREFERENCE_GET = "preference_get"; + public static final String METHOD_PREFERENCE_SET = "preference_set"; + + public static final String METHOD_CHANNEL_ABANDON = "channel_abandon"; + public static final String METHOD_CHANNEL_CREATE = "channel_create"; + public static final String METHOD_CHANNEL_UPDATE = "channel_update"; + + + public static final String METHOD_CLAIM_LIST = "claim_list"; + public static final String METHOD_PURCHASE_LIST = "purchase_list"; + public static final String METHOD_STREAM_ABANDON = "stream_abandon"; + public static final String METHOD_STREAM_REPOST = "stream_repost"; + + public static KeyStore KEYSTORE; + public static boolean SDK_READY = false; + + public static void startupInit() { + abandonedClaimIds = new ArrayList<>(); + ownChannels = new ArrayList<>(); + ownClaims = new ArrayList<>(); + knownTags = new ArrayList<>(); + followedTags = new ArrayList<>(); + } + + public static void parseStatus(String response) { + try { + JSONObject json = parseSdkResponse(response); + INSTALLATION_ID = json.getString("installation_id"); + if (json.has("lbry_id")) { + // if DHT is not enabled, lbry_id won't be set + NODE_ID = json.getString("lbry_id"); + } + IS_STATUS_PARSED = true; + } catch (JSONException | LbryResponseException ex) { + // pass + android.util.Log.e(TAG, "Could not parse status response.", ex); + } + } + + public static Response apiCall(String method) throws LbryRequestException { + return apiCall(method, null); + } + + public static Response apiCall(String method, Map params) throws LbryRequestException { + return apiCall(method, params, SDK_CONNECTION_STRING); + } + + public static Response apiCall(String method, Map params, String connectionString) throws LbryRequestException { + long counter = new Double(System.currentTimeMillis() / 1000.0).longValue(); + JSONObject requestParams = buildJsonParams(params); + JSONObject requestBody = new JSONObject(); + try { + requestBody.put("jsonrpc", "2.0"); + requestBody.put("method", method); + requestBody.put("params", requestParams); + requestBody.put("counter", counter); + } catch (JSONException ex) { + throw new LbryRequestException("Could not build the JSON request body.", ex); + } + + RequestBody body = RequestBody.create(requestBody.toString(), Helper.JSON_MEDIA_TYPE); + Request request = new Request.Builder().url(connectionString).post(body).build(); + OkHttpClient client = new OkHttpClient.Builder(). + writeTimeout(300, TimeUnit.SECONDS). + readTimeout(300, TimeUnit.SECONDS). + build(); + + try { + return client.newCall(request).execute(); + } catch (IOException ex) { + throw new LbryRequestException(String.format("\"%s\" method to %s failed", method, connectionString), ex); + } + } + + public static JSONObject buildJsonParams(Map params) { + JSONObject jsonParams = new JSONObject(); + if (params != null) { + try { + for (Map.Entry param : params.entrySet()) { + Object value = param.getValue(); + if (value instanceof List) { + value = Helper.jsonArrayFromList((List) value); + } + if (value instanceof Double) { + jsonParams.put(param.getKey(), (double) value); + } else { + jsonParams.put(param.getKey(), value == null ? JSONObject.NULL : value); + } + } + } catch (JSONException ex) { + // pass + } + } + + return jsonParams; + } + + public static Object parseResponse(Response response) throws LbryResponseException { + String responseString = null; + try { + responseString = response.body().string(); + JSONObject json = new JSONObject(responseString); + if (response.code() >= 200 && response.code() < 300) { + if (json.has("result")) { + if (json.isNull("result")) { + return null; + } + return json.get("result"); + } else { + processErrorJson(json); + } + } + + processErrorJson(json); + } catch (JSONException | IOException ex) { + throw new LbryResponseException(String.format("Could not parse response: %s", responseString), ex); + } + + return null; + } + + private static void processErrorJson(JSONObject json) throws JSONException, LbryResponseException { + if (json.has("error")) { + String errorMessage = null; + Object jsonError = json.get("error"); + if (jsonError instanceof String) { + errorMessage = jsonError.toString(); + } else { + errorMessage = ((JSONObject) jsonError).getString("message"); + } + throw new LbryResponseException(!Helper.isNullOrEmpty(errorMessage) ? errorMessage : json.getString("error")); + } else { + throw new LbryResponseException("Protocol error with unknown response signature."); + } + } + + public static JSONObject parseSdkResponse(String responseString) throws LbryResponseException { + try { + JSONObject json = new JSONObject(responseString); + if (json.has("error")) { + String errorMessage = null; + Object jsonError = json.get("error"); + if (jsonError instanceof String) { + errorMessage = jsonError.toString(); + } else { + errorMessage = ((JSONObject) jsonError).getString("message"); + } + throw new LbryResponseException(json.getString("error")); + } + + return json; + } catch (JSONException ex) { + throw new LbryResponseException(String.format("Could not parse response: %s", responseString), ex); + } + } + + /** + * API Calls + */ + public static Claim resolve(String url, String connectionString) throws ApiCallException { + List results = resolve(Arrays.asList(url), connectionString); + return results.size() > 0 ? results.get(0) : null; + } + public static List resolve(List urls, String connectionString) throws ApiCallException { + List claims = new ArrayList<>(); + Map params = new HashMap<>(); + params.put("urls", urls); + try { + JSONObject result = (JSONObject) parseResponse(apiCall(METHOD_RESOLVE, params, connectionString)); + Iterator keys = result.keys(); + if (keys != null) { + while (keys.hasNext()) { + Claim claim = Claim.fromJSONObject(result.getJSONObject(keys.next())); + claims.add(claim); + + addClaimToCache(claim); + } + } + } catch (LbryRequestException | LbryResponseException | JSONException ex) { + throw new ApiCallException("Could not execute resolve call", ex); + } + + return claims; + } + public static List transactionList(int page, int pageSize) throws ApiCallException { + List transactions = new ArrayList<>(); + Map params = new HashMap<>(); + if (page > 0) { + params.put("page", page); + } + if (pageSize > 0) { + params.put("page_size", pageSize); + } + try { + JSONObject result = (JSONObject) parseResponse(apiCall(METHOD_TRANSACTION_LIST, params, SDK_CONNECTION_STRING)); + JSONArray items = result.getJSONArray("items"); + for (int i = 0; i < items.length(); i++) { + Transaction tx = Transaction.fromJSONObject(items.getJSONObject(i)); + transactions.add(tx); + } + } catch (LbryRequestException | LbryResponseException | JSONException ex) { + throw new ApiCallException("Could not execute transaction_list call", ex); + } + + return transactions; + } + + public static LbryFile get(boolean saveFile) throws ApiCallException { + LbryFile file = null; + Map params = new HashMap<>(); + params.put("save_file", saveFile); + try { + JSONObject result = (JSONObject) parseResponse(apiCall(METHOD_GET, params)); + file = LbryFile.fromJSONObject(result); + + if (file != null) { + String fileClaimId = file.getClaimId(); + if (!Helper.isNullOrEmpty(fileClaimId)) { + ClaimCacheKey key = new ClaimCacheKey(); + key.setClaimId(fileClaimId); + if (claimCache.containsKey(key)) { + claimCache.get(key).setFile(file); + } + } + } + } catch (LbryRequestException | LbryResponseException ex) { + throw new ApiCallException("Could not execute resolve call", ex); + } + + return file; + } + + public static List fileList(String claimId, boolean downloads, int page, int pageSize) throws ApiCallException { + List files = new ArrayList<>(); + Map params = new HashMap<>(); + if (!Helper.isNullOrEmpty(claimId)) { + params.put("claim_id", claimId); + } + if (downloads) { + params.put("download_path", null); + params.put("comparison", "ne"); + } + if (page > 0) { + params.put("page", page); + } + if (pageSize > 0) { + params.put("page_size", pageSize); + } + try { + JSONObject result = (JSONObject) parseResponse(apiCall(METHOD_FILE_LIST, params)); + JSONArray items = result.getJSONArray("items"); + for (int i = 0; i < items.length(); i++) { + JSONObject fileObject = items.getJSONObject(i); + LbryFile file = LbryFile.fromJSONObject(fileObject); + files.add(file); + + String fileClaimId = file.getClaimId(); + if (!Helper.isNullOrEmpty(fileClaimId)) { + ClaimCacheKey key = new ClaimCacheKey(); + key.setClaimId(fileClaimId); + if (claimCache.containsKey(key)) { + claimCache.get(key).setFile(file); + } + } + } + } catch (LbryRequestException | LbryResponseException | JSONException ex) { + throw new ApiCallException("Could not execute resolve call", ex); + } + + return files; + } + + private static final String[] listParamTypes = new String[] { + "any_tags", "channel_ids", "order_by", "not_tags", "not_channel_ids", "urls" + }; + + public static Map buildClaimSearchOptions( + String claimType, + List anyTags, + List notTags, + List channelIds, + List notChannelIds, + List orderBy, + String releaseTime, + int page, + int pageSize) { + Map options = new HashMap<>(); + if (!Helper.isNullOrEmpty(claimType)) { + options.put("claim_type", claimType); + } + options.put("no_totals", true); + options.put("page", page); + options.put("page_size", pageSize); + if (!Helper.isNullOrEmpty(releaseTime)) { + options.put("release_time", releaseTime); + } + + addClaimSearchListOption("any_tags", anyTags, options); + addClaimSearchListOption("not_tags", notTags, options); + addClaimSearchListOption("channel_ids", channelIds, options); + addClaimSearchListOption("not_channel_ids", notChannelIds, options); + addClaimSearchListOption("order_by", orderBy, options); + + return options; + } + + private static void addClaimSearchListOption(String key, List list, Map options) { + if (list != null && list.size() > 0) { + options.put(key, list); + } + } + + public static List claimSearch(Map options, String connectionString) throws ApiCallException { + if (claimSearchCache.containsKey(options)) { + ClaimSearchCacheValue value = claimSearchCache.get(options); + if (!value.isExpired(TTL_CLAIM_SEARCH_VALUE)) { + return claimSearchCache.get(options).getClaims(); + } + } + + List claims = new ArrayList<>(); + try { + JSONObject result = (JSONObject) parseResponse(apiCall(METHOD_CLAIM_SEARCH, options, connectionString)); + JSONArray items = result.getJSONArray("items"); + if (items != null) { + for (int i = 0; i < items.length(); i++) { + Claim claim = Claim.fromJSONObject(items.getJSONObject(i)); + claims.add(claim); + + addClaimToCache(claim); + } + } + + claimSearchCache.put(options, new ClaimSearchCacheValue(claims, System.currentTimeMillis())); + } catch (LbryRequestException | LbryResponseException | JSONException ex) { + throw new ApiCallException("Could not execute resolve call", ex); + } + + return claims; + } + + public static Map buildSingleParam(String key, Object value) { + Map params = new HashMap<>(); + params.put(key, value); + return params; + } + + /** + * Call to return a generic JSONObject which can be further parsed as required + * @param method + * @param params + * @return + */ + public static Object genericApiCall(String method, Map params) throws ApiCallException { + Object response = null; + try { + response = parseResponse(apiCall(method, params)); + } catch (LbryRequestException | LbryResponseException ex) { + throw new ApiCallException(String.format("Could not execute %s call: %s", method, ex.getMessage()), ex); + } + return response; + } + public static Object genericApiCall(String method) throws ApiCallException { + return genericApiCall(method, null); + } + public static void addFollowedTag(Tag tag) { + synchronized (lock) { + if (!followedTags.contains(tag)) { + followedTags.add(tag); + } + } + } + public static void removeFollowedTag(Tag tag) { + synchronized (lock) { + followedTags.remove(tag); + } + } + public static void addKnownTag(Tag tag) { + synchronized (lock) { + if (!knownTags.contains(tag)) { + knownTags.add(tag); + } + } + } + + public static void addClaimToCache(Claim claim) { + ClaimCacheKey fullKey = ClaimCacheKey.fromClaim(claim); + ClaimCacheKey shortUrlKey = ClaimCacheKey.fromClaimShortUrl(claim); + ClaimCacheKey permanentUrlKey = ClaimCacheKey.fromClaimPermanentUrl(claim); + claimCache.put(fullKey, claim); + claimCache.put(permanentUrlKey, claim); + if (!Helper.isNullOrEmpty(shortUrlKey.getUrl())) { + claimCache.put(shortUrlKey, claim); + } + } + + public static void unsetFilesForCachedClaims(List claimIds) { + for (String claimId : claimIds) { + ClaimCacheKey key = new ClaimCacheKey(); + key.setClaimId(claimId); + if (claimCache.containsKey(key)) { + claimCache.get(key).setFile(null); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/utils/LbryAnalytics.java b/app/src/main/java/io/lbry/browser/utils/LbryAnalytics.java new file mode 100644 index 00000000..ce20980c --- /dev/null +++ b/app/src/main/java/io/lbry/browser/utils/LbryAnalytics.java @@ -0,0 +1,53 @@ +package io.lbry.browser.utils; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; + +import com.google.firebase.analytics.FirebaseAnalytics; + +public class LbryAnalytics { + + public static final String EVENT_APP_EXCEPTION = "app_exception"; + public static final String EVENT_APP_LAUNCH = "app_launch"; + public static final String EVENT_EMAIL_ADDED = "email_added"; + public static final String EVENT_EMAIL_VERIFIED = "email_verified"; + public static final String EVENT_FIRST_RUN_COMPLETED = "first_run_completed"; + public static final String EVENT_FIRST_USER_AUTH = "first_user_auth"; + public static final String EVENT_LBRY_NOTIFICATION_OPEN = "lbry_notification_open"; + public static final String EVENT_LBRY_NOTIFICATION_RECEIVE = "lbry_notification_receive"; + public static final String EVENT_OPEN_FILE_PAGE = "open_file_page"; + public static final String EVENT_PLAY = "play"; + public static final String EVENT_PURCHASE_URI = "purchase_uri"; + public static final String EVENT_REWARD_ELIGIBILITY_COMPLETED = "reward_eligibility_completed"; + public static final String EVENT_TAG_FOLLOW = "tag_follow"; + public static final String EVENT_TAG_UNFOLLOW = "tag_unfollow"; + public static final String EVENT_PUBLISH = "publish"; + public static final String EVENT_CHANNEL_CREATE = "channel_create"; + public static final String EVENT_SEARCH = "search"; + + private static FirebaseAnalytics analytics; + + public static void init(Context context) { + analytics = FirebaseAnalytics.getInstance(context); + } + + public static void setCurrentScreen(Activity activity, String name, String className) { + analytics.setCurrentScreen(activity, name, className); + } + + public static void logEvent(String name) { + logEvent(name, null); + } + + public static void logEvent(String name, Bundle bundle) { + analytics.logEvent(name, bundle); + } + + public static void logException(String message, String exceptionName) { + Bundle bundle = new Bundle(); + bundle.putString("message", message); + bundle.putString("name", exceptionName); + logEvent("app_exception", bundle); + } +} diff --git a/app/src/main/java/io/lbry/browser/utils/LbryUri.java b/app/src/main/java/io/lbry/browser/utils/LbryUri.java new file mode 100644 index 00000000..5d9f815f --- /dev/null +++ b/app/src/main/java/io/lbry/browser/utils/LbryUri.java @@ -0,0 +1,336 @@ +package io.lbry.browser.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.lbry.browser.exceptions.LbryUriException; +import lombok.Data; + +@Data +public class LbryUri { + public static final String LBRY_TV_BASE_URL = "https://lbry.tv"; + public static final String PROTO_DEFAULT = "lbry://"; + public static final String REGEX_INVALID_URI = "[ =&#:$@%?;/\\\\\"<>%\\{\\}|^~\\[\\]`\u0000-\u0008\u000b-\u000c\u000e-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]"; + public static final String REGEX_ADDRESS = "^(b|r)(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$"; + public static final int CHANNEL_NAME_MIN_LENGTH = 1; + public static final int CLAIM_ID_MAX_LENGTH = 40; + + private static final String REGEX_PART_PROTOCOL = "^((?:lbry://)?)"; + private static final String REGEX_PART_STREAM_OR_CHANNEL_NAME = "([^:$#/]*)"; + private static final String REGEX_PART_MODIFIER_SEPARATOR = "([:$#]?)([^/]*)"; + private static final String QUERY_STRING_BREAKER = "^([\\S]+)([?][\\S]*)"; + private static final Pattern PATTERN_SEPARATE_QUERY_STRING = Pattern.compile(QUERY_STRING_BREAKER); + + private String path; + private boolean isChannel; + private String streamName; + private String streamClaimId; + private String channelName; + private String channelClaimId; + private int primaryClaimSequence; + private int secondaryClaimSequence; + private int primaryBidPosition; + private int secondaryBidPosition; + + private String claimName; + private String claimId; + private String contentName; + private String queryString; + + private boolean isChannelUrl() { + return (!Helper.isNullOrEmpty(channelName) && Helper.isNullOrEmpty(streamName)) || (!Helper.isNullOrEmpty(claimName) && claimName.startsWith("@")); + } + + public static boolean isNameValid(String name) { + return !Pattern.compile(REGEX_INVALID_URI).matcher(name).find(); + } + + public static LbryUri tryParse(String url) { + try { + return parse(url, false); + } catch (LbryUriException ex) { + return null; + } + } + public static LbryUri parse(String url) throws LbryUriException { + return parse(url, false); + } + public static LbryUri parse(String url, boolean requireProto) throws LbryUriException { + Pattern componentsPattern = Pattern.compile(String.format("%s%s%s(/?)%s%s", + REGEX_PART_PROTOCOL, + REGEX_PART_STREAM_OR_CHANNEL_NAME, + REGEX_PART_MODIFIER_SEPARATOR, + REGEX_PART_STREAM_OR_CHANNEL_NAME, + REGEX_PART_MODIFIER_SEPARATOR)); + + String cleanUrl = url, queryString = null; + Matcher qsMatcher = PATTERN_SEPARATE_QUERY_STRING.matcher(url); + if (qsMatcher.matches()) { + queryString = qsMatcher.group(2); + cleanUrl = !Helper.isNullOrEmpty(queryString) ? url.substring(0, url.indexOf(queryString)) : url; + if (queryString != null && queryString.length() > 0) { + queryString = queryString.substring(1); + } + } + + List components = new ArrayList<>(); + Matcher matcher = componentsPattern.matcher(cleanUrl); + if (matcher.matches()) { + // Note: For Java regex, group index 0 is always the full match + for (int i = 1; i <= matcher.groupCount(); i++) { + components.add(matcher.group(i)); + } + } + + if (components.size() == 0) { + throw new LbryUriException("Regular expression error occurred while trying to parse the value"); + } + + // components[0] = proto + // components[1] = streamNameOrChannelName + // components[2] = primaryModSeparator + // components[3] = primaryModValue + // components[4] = pathSep + // components[5] = possibleStreamName + // components[6] = secondaryModSeparator + // components[7] = secondaryModValue + if (requireProto && Helper.isNullOrEmpty(components.get(0))) { + throw new LbryUriException("LBRY URLs must include a protocol prefix (lbry://)."); + } + + if (Helper.isNullOrEmpty(components.get(1))) { + throw new LbryUriException("URL does not include name."); + } + + for (String component : components.subList(1, components.size())) { + if (component.indexOf(' ') > -1) { + throw new LbryUriException("URL cannot include a space."); + } + } + + String streamOrChannelName = components.get(1); + String primaryModSeparator = components.get(2); + String primaryModValue = components.get(3); + String possibleStreamName = components.get(5); + String secondaryModSeparator = components.get(6); + String secondaryModValue = components.get(7); + + boolean includesChannel = streamOrChannelName.startsWith("@"); + boolean isChannel = includesChannel && Helper.isNullOrEmpty(possibleStreamName); + String channelName = includesChannel && streamOrChannelName.length() > 1 ? streamOrChannelName.substring(1) : null; + if (includesChannel) { + if (Helper.isNullOrEmpty(channelName)) { + throw new LbryUriException("No channel name after @."); + } + if (channelName.length() < CHANNEL_NAME_MIN_LENGTH) { + throw new LbryUriException(String.format("Channel names must be at least %d character long.", CHANNEL_NAME_MIN_LENGTH)); + } + } + + UriModifier primaryMod = null, secondaryMod = null; + if (!Helper.isNullOrEmpty(primaryModSeparator) && !Helper.isNullOrEmpty(primaryModValue)) { + primaryMod = UriModifier.parse(primaryModSeparator, primaryModValue); + } + if (!Helper.isNullOrEmpty(secondaryModSeparator) && !Helper.isNullOrEmpty(secondaryModValue)) { + secondaryMod = UriModifier.parse(secondaryModSeparator, secondaryModValue); + } + String streamName = includesChannel ? possibleStreamName : streamOrChannelName; + String streamClaimId = (includesChannel && secondaryMod != null) ? + secondaryMod.getClaimId() : primaryMod != null ? primaryMod.getClaimId() : null; + String channelClaimId = (includesChannel && primaryMod != null) ? primaryMod.getClaimId() : null; + + LbryUri uri = new LbryUri(); + uri.setChannel(isChannel); + uri.setPath(Helper.join(components.subList(1, components.size()), "")); + uri.setStreamName(streamName); + uri.setStreamClaimId(streamClaimId); + uri.setChannelName(channelName); + uri.setChannelClaimId(channelClaimId); + uri.setPrimaryClaimSequence(primaryMod != null ? primaryMod.getClaimSequence() : -1); + uri.setSecondaryClaimSequence(secondaryMod != null ? secondaryMod.getClaimSequence() : -1); + uri.setPrimaryBidPosition(primaryMod != null ? primaryMod.getBidPosition() : -1); + uri.setSecondaryBidPosition(secondaryMod != null ? secondaryMod.getBidPosition() : -1); + + // Values that will not work properly with canonical urls + uri.setClaimName(streamOrChannelName); + uri.setClaimId(primaryMod != null ? primaryMod.getClaimId() : null); + uri.setContentName(streamName); + uri.setQueryString(queryString); + return uri; + } + + public String build(boolean includeProto, String protoDefault, boolean vanity) { + String formattedChannelName = null; + if (channelName != null) { + formattedChannelName = channelName.startsWith("@") ? channelName : String.format("@%s", channelName); + } + String primaryClaimName = claimName; + if (Helper.isNullOrEmpty(primaryClaimName)) { + primaryClaimName = contentName; + } + if (Helper.isNullOrEmpty(primaryClaimName)) { + primaryClaimName = formattedChannelName; + } + if (Helper.isNullOrEmpty(primaryClaimName)) { + primaryClaimName = streamName; + } + + String primaryClaimId = claimId; + if (Helper.isNullOrEmpty(primaryClaimId)) { + primaryClaimId = !Helper.isNullOrEmpty(formattedChannelName) ? channelClaimId : streamClaimId; + } + + StringBuilder sb = new StringBuilder(); + if (includeProto) { + sb.append(protoDefault); + } + sb.append(primaryClaimName); + if (vanity) { + return sb.toString(); + } + + String secondaryClaimName = null; + if (Helper.isNullOrEmpty(claimName) && !Helper.isNullOrEmpty(contentName)) { + secondaryClaimName = contentName; + } + if (Helper.isNullOrEmpty(secondaryClaimName)) { + secondaryClaimName = !Helper.isNullOrEmpty(formattedChannelName) ? streamName : null; + } + String secondaryClaimId = !Helper.isNullOrEmpty(secondaryClaimName) ? streamClaimId : null; + + if (!Helper.isNullOrEmpty(primaryClaimId)) { + sb.append('#').append(primaryClaimId); + } + if (primaryClaimSequence > 0) { + sb.append(':').append(primaryClaimSequence); + } + if (primaryBidPosition > 0) { + sb.append('$').append(primaryBidPosition); + } + if (!Helper.isNullOrEmpty(secondaryClaimName)) { + sb.append('/').append(secondaryClaimName); + } + if (!Helper.isNullOrEmpty(secondaryClaimId)) { + sb.append('#').append(secondaryClaimId); + } + if (secondaryClaimSequence > 0) { + sb.append(':').append(secondaryClaimSequence); + } + if (secondaryBidPosition > 0) { + sb.append('$').append(secondaryBidPosition); + } + + return sb.toString(); + } + + public String toTvString() { + String formattedChannelName = null; + if (channelName != null) { + formattedChannelName = channelName.startsWith("@") ? channelName : String.format("@%s", channelName); + } + String primaryClaimName = claimName; + if (Helper.isNullOrEmpty(primaryClaimName)) { + primaryClaimName = contentName; + } + if (Helper.isNullOrEmpty(primaryClaimName)) { + primaryClaimName = formattedChannelName; + } + if (Helper.isNullOrEmpty(primaryClaimName)) { + primaryClaimName = streamName; + } + + String primaryClaimId = claimId; + if (Helper.isNullOrEmpty(primaryClaimId)) { + primaryClaimId = !Helper.isNullOrEmpty(formattedChannelName) ? channelClaimId : streamClaimId; + } + + StringBuilder sb = new StringBuilder(); + sb.append(LBRY_TV_BASE_URL).append('/'); + sb.append(primaryClaimName); + + String secondaryClaimName = null; + if (Helper.isNullOrEmpty(claimName) && !Helper.isNullOrEmpty(contentName)) { + secondaryClaimName = contentName; + } + if (Helper.isNullOrEmpty(secondaryClaimName)) { + secondaryClaimName = !Helper.isNullOrEmpty(formattedChannelName) ? streamName : null; + } + String secondaryClaimId = !Helper.isNullOrEmpty(secondaryClaimName) ? streamClaimId : null; + + if (!Helper.isNullOrEmpty(primaryClaimId)) { + sb.append(':').append(primaryClaimId); + } + if (!Helper.isNullOrEmpty(secondaryClaimName)) { + sb.append('/').append(secondaryClaimName); + } + if (!Helper.isNullOrEmpty(secondaryClaimId)) { + sb.append(':').append(secondaryClaimId); + } + return sb.toString(); + } + + public static String normalize(String url) throws LbryUriException { + return parse(url).toString(); + } + + public String toVanityString() { + return build(true, PROTO_DEFAULT, true); + } + public String toString() { + return build(true, PROTO_DEFAULT, false); + } + public int hashCode() { + return toString().hashCode(); + } + public boolean equals(Object o) { + if (o == null || !(o instanceof LbryUri)) { + return false; + } + return toString().equalsIgnoreCase(o.toString()); + } + + @Data + public static class UriModifier { + private String claimId; + private int claimSequence; + private int bidPosition; + + public UriModifier(String claimId, int claimSequence, int bidPosition) { + this.claimId = claimId; + this.claimSequence = claimSequence; + this.bidPosition = bidPosition; + } + + public static UriModifier parse(String modSeparator, String modValue) throws LbryUriException { + String claimId = null; + int claimSequence = 0, bidPosition = 0; + if (!Helper.isNullOrEmpty(modSeparator)) { + if (Helper.isNullOrEmpty(modValue)) { + throw new LbryUriException(String.format("No modifier provided after separator %s", modSeparator)); + } + + if ("#".equals(modSeparator)) { + claimId = modValue; + } else if (":".equals(modSeparator)) { + claimSequence = Helper.parseInt(modValue, -1); + } else if ("$".equals(modSeparator)) { + bidPosition = Helper.parseInt(modValue, -1); + } + } + + if (!Helper.isNullOrEmpty(claimId) && (claimId.length() > CLAIM_ID_MAX_LENGTH || !claimId.matches("^[0-9a-f]+$"))) { + throw new LbryUriException(String.format("Invalid claim ID %s", claimId)); + } + if (claimSequence == -1) { + throw new LbryUriException("Claim sequence must be a number"); + } + if (bidPosition == -1) { + throw new LbryUriException("Bid position must be a number"); + } + + return new UriModifier(claimId, claimSequence, bidPosition); + } + } +} diff --git a/app/src/main/java/io/lbry/browser/utils/Lbryio.java b/app/src/main/java/io/lbry/browser/utils/Lbryio.java new file mode 100644 index 00000000..02e9b07d --- /dev/null +++ b/app/src/main/java/io/lbry/browser/utils/Lbryio.java @@ -0,0 +1,345 @@ +package io.lbry.browser.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.util.Log; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.exceptions.LbryioRequestException; +import io.lbry.browser.exceptions.LbryioResponseException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.WalletSync; +import io.lbry.browser.model.lbryinc.Reward; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.model.lbryinc.User; +import io.lbry.lbrysdk.Utils; +import lombok.Data; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +@Data +public final class Lbryio { + + // TODO: Get this from the bundled aar + public static String SDK_VERSION = "0.74.0"; + + public static User currentUser; + public static boolean userHasSyncedWallet = false; + public static String lastRemoteHash; + public static WalletSync lastWalletSync; + public static final Object lock = new Object(); + + public static final String TAG = "Lbryio"; + public static final String CONNECTION_STRING = "https://api.lbry.com"; + public static final String AUTH_TOKEN_PARAM = "auth_token"; + public static List subscriptions = new ArrayList<>(); + public static List cacheResolvedSubscriptions = new ArrayList<>(); + public static double LBCUSDRate = 0; + public static String AUTH_TOKEN; + private static boolean generatingAuthToken = false; + + public static List allRewards = new ArrayList<>(); + public static List unclaimedRewards = new ArrayList<>(); + public static double totalUnclaimedRewardAmount = 0; + + public static Response call(String resource, String action, Context context) throws LbryioRequestException, LbryioResponseException { + return call(resource, action, null, Helper.METHOD_GET, context); + } + + public static Response call(String resource, String action, Map options, Context context) throws LbryioRequestException, LbryioResponseException { + return call(resource, action, options, Helper.METHOD_GET, context); + } + + public static Response call(String resource, String action, Map options, String method, Context context) + throws LbryioRequestException, LbryioResponseException { + String authToken = AUTH_TOKEN; + if (Helper.isNullOrEmpty(authToken) && !generatingAuthToken) { + // Only call getAuthToken if not calling /user/new + authToken = getAuthToken(context); + } + + String url = String.format("%s/%s/%s", CONNECTION_STRING, resource, action); + if (Helper.METHOD_GET.equalsIgnoreCase(method)) { + Uri.Builder uriBuilder = Uri.parse(url).buildUpon(); + if (!Helper.isNullOrEmpty(authToken)) { + uriBuilder.appendQueryParameter(AUTH_TOKEN_PARAM, authToken); + } + if (options != null) { + for (Map.Entry option : options.entrySet()) { + uriBuilder.appendQueryParameter(option.getKey(), option.getValue()); + } + } + url = uriBuilder.build().toString(); + } + + Request.Builder builder = new Request.Builder().url(url); + if (Helper.METHOD_POST.equalsIgnoreCase(method)) { + RequestBody body = RequestBody.create(buildQueryString(authToken, options), Helper.FORM_MEDIA_TYPE); + builder.post(body); + } + + Request request = builder.build(); + OkHttpClient client = new OkHttpClient(); + try { + return client.newCall(request).execute(); + } catch (IOException ex) { + throw new LbryioRequestException(String.format("%s request to %s/%s failed", method, resource, action), ex); + } + } + + private static String buildQueryString(String authToken, Map options) { + StringBuilder qs = new StringBuilder(); + try { + String delim = ""; + if (!Helper.isNullOrEmpty(authToken)) { + qs.append(AUTH_TOKEN_PARAM).append("=").append(URLEncoder.encode(authToken, "UTF8")); + delim = "&"; + } + + if (options != null) { + for (Map.Entry option : options.entrySet()) { + qs.append(delim).append(option.getKey()).append("=").append(URLEncoder.encode(Helper.isNull(option.getValue()) ? "" : option.getValue(), "UTF8")); + delim = "&"; + } + } + } catch (UnsupportedEncodingException ex) { + // pass + } + + return qs.toString(); + } + + public static String getAuthToken(Context context) throws LbryioRequestException, LbryioResponseException { + // fetch a new auth token + if (Helper.isNullOrEmpty(Lbry.INSTALLATION_ID)) { + throw new LbryioRequestException("The LBRY installation ID is not set."); + } + + generatingAuthToken = true; + + Map options = new HashMap<>(); + options.put("auth_token", ""); + options.put("language", "en"); + options.put("app_id", Lbry.INSTALLATION_ID); + Response response = Lbryio.call("user", "new", options, "post", context); + try { + JSONObject json = (JSONObject) parseResponse(response); + if (!json.has(AUTH_TOKEN_PARAM)) { + throw new LbryioResponseException("auth_token was not set in the response"); + } + + AUTH_TOKEN = json.getString(AUTH_TOKEN_PARAM); + broadcastAuthTokenGenerated(context); + } catch (JSONException | ClassCastException ex) { + throw new LbryioResponseException("auth_token was not set in the response", ex); + } finally { + generatingAuthToken = false; + } + + return AUTH_TOKEN; + } + + public static Object parseResponse(Response response) throws LbryioResponseException { + String responseString = null; + try { + responseString = response.body().string(); + JSONObject json = new JSONObject(responseString); + if (response.code() >= 200 && response.code() < 300) { + if (json.isNull("data")) { + return null; + } + return json.get("data"); + } + + if (json.has("error")) { + throw new LbryioResponseException(json.getString("error"), response.code()); + } else { + throw new LbryioResponseException("Unknown API error signature.", response.code()); + } + } catch (JSONException | IOException ex) { + throw new LbryioResponseException(String.format("Could not parse response: %s", responseString), ex); + } + } + + public static User fetchCurrentUser(Context context) { + try { + Response response = Lbryio.call("user", "me", context); + JSONObject object = (JSONObject) parseResponse(response); + Type type = new TypeToken(){}.getType(); + Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + User user = gson.fromJson(object.toString(), type); + return user; + } catch (LbryioRequestException | LbryioResponseException | ClassCastException | IllegalStateException ex) { + LbryAnalytics.logException(String.format("/user/me failed: %s", ex.getMessage()), ex.getClass().getName()); + android.util.Log.e(TAG, "Could not retrieve the current user", ex); + return null; + } + } + + public static void newInstall(Context context) { + String appVersion = ""; + if (context != null) { + try { + PackageManager manager = context.getPackageManager(); + PackageInfo info = manager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES); + appVersion = info.versionName; + } catch (PackageManager.NameNotFoundException ex) { + + } + } + + Map options = new HashMap<>(); + if (context instanceof MainActivity) { + String firebaseToken = ((MainActivity) context).getFirebaseMessagingToken(); + if (!Helper.isNullOrEmpty(firebaseToken)) { + options.put("firebase_token", firebaseToken); + } + } + options.put("app_version", appVersion); + options.put("app_id", Lbry.INSTALLATION_ID); + options.put("node_id", ""); + options.put("daemon_version", SDK_VERSION); + options.put("operating_system", "android"); + options.put("platform", String.format("Android %s (API %d)", Utils.getAndroidRelease(), Utils.getAndroidSdk())); + try { + JSONObject response = (JSONObject) parseResponse(call("install", "new", options, Helper.METHOD_POST, context)); + } catch (LbryioRequestException | LbryioResponseException | ClassCastException ex) { + // pass + Log.e(TAG, String.format("install/new failed: %s", ex.getMessage()), ex); + } + } + + public static String getSignedInEmail() { + return currentUser != null ? currentUser.getPrimaryEmail() : ""; + } + + public static boolean isSignedIn() { + return currentUser != null && currentUser.isHasVerifiedEmail(); + } + + public static void authenticate(Context context) { + User user = fetchCurrentUser(context); + if (user != null) { + currentUser = user; + if (context != null) { + context.sendBroadcast(new Intent(MainActivity.ACTION_USER_AUTHENTICATION_SUCCESS)); + } + } else { + if (context != null) { + context.sendBroadcast(new Intent(MainActivity.ACTION_USER_AUTHENTICATION_FAILED)); + } + } + } + + public static void loadExchangeRate() { + try { + JSONObject response = (JSONObject) parseResponse(Lbryio.call("lbc", "exchange_rate", null)); + LBCUSDRate = Helper.getJSONDouble("lbc_usd", 0, response); + } catch (LbryioResponseException | LbryioRequestException | ClassCastException ex) { + // pass + } + } + + private static void broadcastAuthTokenGenerated(Context context) { + try { + if (context != null) { + String encryptedAuthToken = Utils.encrypt(AUTH_TOKEN.getBytes("UTF8"), context, Lbry.KEYSTORE); + Intent intent = new Intent(MainActivity.ACTION_AUTH_TOKEN_GENERATED); + intent.putExtra("authToken", encryptedAuthToken); + context.sendBroadcast(intent); + } + } catch (Exception ex) { + android.util.Log.e(TAG, "Error sending encrypted auth token action broadcast", ex); + // pass + } + } + + public static Map buildSingleParam(String key, String value) { + Map params = new HashMap<>(); + params.put(key, value); + return params; + } + + public static void setLastWalletSync(WalletSync walletSync) { + synchronized (lock) { + lastWalletSync = walletSync; + } + } + + public static void setLastRemoteHash(String hash) { + synchronized (lock) { + lastRemoteHash = hash; + } + } + + public static void addSubscription(Subscription subscription) { + synchronized (lock) { + if (!subscriptions.contains(subscription)) { + subscriptions.add(subscription); + } + } + } + public static void removeSubscription(Subscription subscription) { + synchronized (lock) { + subscriptions.remove(subscription); + } + } + public static void addCachedResolvedSubscription(Claim claim) { + synchronized (lock) { + if (!cacheResolvedSubscriptions.contains(claim)) { + cacheResolvedSubscriptions.add(claim); + } + } + } + public static void removeCachedResolvedSubscription(Claim claim) { + synchronized (lock) { + cacheResolvedSubscriptions.remove(claim); + } + } + + public static boolean isFollowing(Subscription subscription) { + return subscriptions.contains(subscription); + } + public static boolean isFollowing(Claim claim) { + return subscriptions.contains(Subscription.fromClaim(claim)); + } + + public static void updateRewardsLists(List rewards) { + synchronized (lock) { + allRewards.clear(); + unclaimedRewards.clear(); + totalUnclaimedRewardAmount = 0; + for (int i = 0; i < rewards.size(); i++) { + Reward reward = rewards.get(i); + allRewards.add(reward); + if (!reward.isClaimed()) { + unclaimedRewards.add(reward); + totalUnclaimedRewardAmount += reward.getRewardAmount(); + } + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/utils/Lighthouse.java b/app/src/main/java/io/lbry/browser/utils/Lighthouse.java new file mode 100644 index 00000000..cd8d4da2 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/utils/Lighthouse.java @@ -0,0 +1,119 @@ +package io.lbry.browser.utils; + +import android.net.Uri; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.exceptions.LbryRequestException; +import io.lbry.browser.exceptions.LbryResponseException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.UrlSuggestion; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class Lighthouse { + public static final String CONNECTION_STRING = "https://lighthouse.lbry.com"; + public static Map> autocompleteCache = new HashMap<>(); + public static Map, List> searchCache = new HashMap<>(); + + private static Map buildSearchOptionsKey(String rawQuery, int size, int from, boolean nsfw, String relatedTo) { + Map options = new HashMap<>(); + options.put("s", rawQuery); + options.put("size", size); + options.put("from", from); + options.put("nsfw", nsfw); + if (!Helper.isNullOrEmpty(relatedTo)) { + options.put("related_to", relatedTo); + } + return options; + } + + public static List search(String rawQuery, int size, int from, boolean nsfw, String relatedTo) throws LbryRequestException, LbryResponseException { + Uri.Builder uriBuilder = Uri.parse(String.format("%s/search", CONNECTION_STRING)).buildUpon(). + appendQueryParameter("s", rawQuery). + appendQueryParameter("resolve", "true"). + appendQueryParameter("nsfw", String.valueOf(nsfw).toLowerCase()). + appendQueryParameter("size", String.valueOf(size)). + appendQueryParameter("from", String.valueOf(from)); + if (!Helper.isNullOrEmpty(relatedTo)) { + uriBuilder.appendQueryParameter("related_to", relatedTo); + } + + Map cacheKey = buildSearchOptionsKey(rawQuery, size, from, nsfw, relatedTo); + if (searchCache.containsKey(cacheKey)) { + return searchCache.get(cacheKey); + } + + List results = new ArrayList<>(); + Request request = new Request.Builder().url(uriBuilder.toString()).build(); + OkHttpClient client = new OkHttpClient(); + try { + Response response = client.newCall(request).execute(); + if (response.code() == 200) { + JSONArray array = new JSONArray(response.body().string()); + for (int i = 0; i < array.length(); i++) { + Claim claim = Claim.fromSearchJSONObject(array.getJSONObject(i)); + results.add(claim); + } + searchCache.put(cacheKey, results); + } else { + throw new LbryResponseException(response.message()); + } + } catch (IOException ex) { + throw new LbryRequestException(String.format("search request for '%s' failed", rawQuery), ex); + } catch (JSONException ex) { + throw new LbryResponseException(String.format("the search response for '%s' could not be parsed", rawQuery), ex); + } + + return results; + } + + public static List autocomplete(String text) throws LbryRequestException, LbryResponseException { + if (autocompleteCache.containsKey(text)) { + return autocompleteCache.get(text); + } + + List suggestions = new ArrayList<>(); + Uri.Builder uriBuilder = Uri.parse(String.format("%s/autocomplete", CONNECTION_STRING)).buildUpon(). + appendQueryParameter("s", text); + Request request = new Request.Builder().url(uriBuilder.toString()).build(); + OkHttpClient client = new OkHttpClient(); + try { + Response response = client.newCall(request).execute(); + if (response.code() == 200) { + JSONArray array = new JSONArray(response.body().string()); + for (int i = 0; i < array.length(); i++) { + String item = array.getString(i); + boolean isChannel = item.startsWith("@"); + LbryUri uri = new LbryUri(); + if (isChannel) { + uri.setChannelName(item); + } else { + uri.setStreamName(item); + } + UrlSuggestion suggestion = new UrlSuggestion(isChannel ? UrlSuggestion.TYPE_CHANNEL : UrlSuggestion.TYPE_FILE, item); + suggestion.setUri(uri); + suggestions.add(suggestion); + } + + autocompleteCache.put(text, suggestions); + } else { + throw new LbryResponseException(response.message()); + } + } catch (IOException ex) { + throw new LbryRequestException(String.format("autocomplete request for '%s' failed", text), ex); + } catch (JSONException ex) { + throw new LbryResponseException(String.format("the autocomplete response for '%s' could not be parsed", text), ex); + } + + return suggestions; + } +} diff --git a/app/src/main/java/io/lbry/browser/utils/Predefined.java b/app/src/main/java/io/lbry/browser/utils/Predefined.java new file mode 100644 index 00000000..55c75711 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/utils/Predefined.java @@ -0,0 +1,1898 @@ +package io.lbry.browser.utils; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.R; +import io.lbry.browser.model.Language; +import io.lbry.browser.model.License; + +public final class Predefined { + public static final List DEFAULT_KNOWN_TAGS = Arrays.asList( + "free speech", + "censorship", + "gaming", + "pop culture", + "entertainment", + "technology", + "music", + "funny", + "education", + "learning", + "news", + "gameplay", + "nature", + "beliefs", + "comedy", + "games", + "film & animation", + "whothinks", + "game", + "weapons", + "blockchain", + "video game", + "sports", + "walkthrough", + "art", + "pc", + "minecraft", + "playthrough", + "economics", + "automotive", + "play", + "tutorial", + "twitch", + "how to", + "ps4", + "bitcoin", + "fortnite", + "commentary", + "lets play", + "fun", + "politics", + "travel", + "food", + "science", + "xbox", + "liberal", + "democrat", + "progressive", + "survival", + "non-profits", + "activism", + "cryptocurrency", + "playstation", + "nintendo", + "government", + "steam", + "podcast", + "gamer", + "horror", + "conservative", + "reaction", + "trailer", + "love", + "cnn", + "republican", + "political", + "hangoutsonair", + "hoa", + "msnbc", + "cbs", + "anime", + "donald trump", + "fiction", + "fox news", + "crypto", + "ethereum", + "call of duty", + "android", + "multiplayer", + "epic", + "rpg", + "adventure", + "secular talk", + "btc", + "atheist", + "atheism", + "video games", + "ps3", + "cod", + "online", + "agnostic", + "movie", + "fps", + "lets", + "mod", + "world", + "reviews", + "sharefactory", + "space", + "pokemon", + "stream", + "hilarious", + "lol", + "sony", + "god", + "dance", + "pvp", + "tech", + "strategy", + "zombies", + "fail", + "film", + "xbox360", + "animation", + "unboxing", + "money", + "wwe", + "mods", + "indie", + "pubg", + "ios", + "history", + "rap", + "mobile", + "trump", + "hack", + "flat earth", + "trap", + "humor", + "vlogging", + "fox", + "news radio", + "facebook", + "edm", + "fitness", + "vaping", + "hip hop", + "secular", + "jesus", + "song", + "vape", + "guitar", + "remix", + "mining", + "daily", + "diy", + "pets", + "videogame", + "death", + "funny moments", + "religion", + "media", + "viral", + "war", + "nbc", + "freedom", + "gold", + "family", + "meme", + "zombie", + "photography", + "chill", + "sniper", + "computer", + "iphone", + "dragon", + "bible", + "pro", + "overwatch", + "litecoin", + "gta", + "house", + "fire", + "bass", + "truth", + "crash", + "mario", + "league of legends", + "wii", + "mmorpg", + "health", + "marvel", + "racing", + "apple", + "instrumental", + "earth", + "destiny", + "satire", + "race", + "training", + "electronic", + "boss", + "roblox", + "family friendly", + "california", + "react", + "christian", + "mmo", + "twitter", + "help", + "star", + "cars", + "random", + "top 10", + "ninja", + "guns", + "linux", + "lessons", + "vegan", + "future", + "dota 2", + "studio", + "star wars", + "shooting", + "nasa", + "rock", + "league", + "subscribe", + "water", + "gta v", + "car", + "samsung", + "music video", + "skyrim", + "dog", + "comics", + "shooter game", + "bo3", + "halloween", + "liberty", + "eth", + "conspiracy", + "knife", + "fashion", + "stories", + "vapor", + "nvidia", + "cute", + "beat", + "nintendo switch", + "fantasy", + "christmas", + "world of warcraft", + "industry", + "cartoon", + "garden", + "animals", + "windows", + "happy", + "magic", + "memes", + "design", + "tactical", + "fallout 4", + "puzzle", + "parody", + "rv", + "beats", + "building", + "disney", + "drone", + "ps2", + "beach", + "metal", + "christianity", + "business", + "mix", + "bo2", + "cover", + "senate", + "4k", + "united states", + "final", + "hero", + "playing", + "dlc", + "ubisoft", + "halo", + "pc gaming", + "raw", + "investing", + "online learning", + "software", + "ark", + "mojang", + "console", + "battle royale", + "canon", + "microsoft", + "camping", + "ufo", + "progressive talk", + "switch", + "fpv", + "arcade", + "school", + "driving", + "bodybuilding", + "drama", + "retro", + "science fiction", + "eggs", + "australia", + "modded", + "rainbow", + "gamers", + "resident evil", + "drawing", + "brasil", + "england", + "hillary clinton", + "singing", + "final fantasy", + "hiphop", + "video blog", + "mature", + "quad", + "noob", + "simulation", + "illuminati", + "poetry", + "dayz", + "manga", + "howto", + "insane", + "press", + "special", + "church", + "ico", + "weird", + "libertarian", + "crafting", + "level", + "comic", + "sandbox", + "daily vlog", + "outdoor", + "black ops", + "sound", + "christ", + "duty", + "juvenile fiction", + "pc game", + "how-to", + "ww2", + "creepy", + "artist", + "galaxy", + "destiny 2", + "new music", + "quest", + "lee", + "pacman", + "super smash bros", + "day", + "survival horror", + "patreon", + "bitcoin price", + "trending", + "open world", + "wii u", + "dope", + "reaper", + "sniping", + "dubstep", + "truck", + "planet", + "dc", + "amazon", + "spirituality", + "universe", + "video game culture", + "community", + "cat", + "aliens", + "tourism", + "altcoins", + "style", + "travel trailer", + "rda", + "gun", + "secret", + "far cry 5", + "auto", + "culture", + "dj", + "mw2", + "lord", + "full time rving", + "role-playing game", + "prank", + "grand theft auto", + "master", + "wrestling", + "sci-fi", + "workout", + "ghost", + "fake news", + "silly", + "season", + "bo4", + "trading", + "extreme", + "economy", + "combat", + "plays", + "muslim", + "pubg mobile", + "clips", + "bo1", + "paypal", + "sims", + "exploration", + "light", + "ripple", + "paranormal", + "football", + "capcom", + "rta", + "discord", + "batman", + "player", + "server", + "anarchy", + "military", + "playlist", + "cosplay", + "rv park", + "rant", + "edit", + "germany", + "reading", + "chris", + "flash", + "loot", + "bitcoin gratis", + "game reviews", + "movies", + "stupid", + "latest news", + "squad gameplay", + "guru", + "timelapse", + "black ops 3", + "holiday", + "soul", + "motivation", + "mw3", + "vacation", + "sega", + "19th century", + "pop", + "sims 4", + "post", + "smok", + "island", + "scotland", + "paladins", + "warrior", + "creepypasta", + "role-playing", + "solar", + "vr", + "animal", + "peace", + "consciousness", + "dota", + "audio", + "mass effect", + "humour", + "first look", + "videogames", + "future bass", + "freestyle", + "hardcore", + "portugal", + "dantdm", + "teaser", + "lbry", + "coronavirus", + "covidcuts", + "covid-19" + ); + public static final List MATURE_TAGS = Arrays.asList("mature", "nsfw", "porn", "xxx"); + public static final List ADJECTIVES = Arrays.asList( + "aback", + "abaft", + "abandoned", + "abashed", + "aberrant", + "abhorrent", + "abiding", + "abject", + "ablaze", + "able", + "abnormal", + "aboard", + "aboriginal", + "abortive", + "abounding", + "abrasive", + "abrupt", + "absent", + "absorbed", + "absorbing", + "abstracted", + "absurd", + "abundant", + "abusive", + "acceptable", + "accessible", + "accidental", + "accurate", + "acid", + "acidic", + "acoustic", + "acrid", + "actually", + "ad", + "hoc", + "adamant", + "adaptable", + "addicted", + "adhesive", + "adjoining", + "adorable", + "adventurous", + "afraid", + "aggressive", + "agonizing", + "agreeable", + "ahead", + "ajar", + "alcoholic", + "alert", + "alike", + "alive", + "alleged", + "alluring", + "aloof", + "amazing", + "ambiguous", + "ambitious", + "amuck", + "amused", + "amusing", + "ancient", + "angry", + "animated", + "annoyed", + "annoying", + "anxious", + "apathetic", + "aquatic", + "aromatic", + "arrogant", + "ashamed", + "aspiring", + "assorted", + "astonishing", + "attractive", + "auspicious", + "automatic", + "available", + "average", + "awake", + "aware", + "awesome", + "awful", + "axiomatic", + "bad", + "barbarous", + "bashful", + "bawdy", + "beautiful", + "befitting", + "belligerent", + "beneficial", + "bent", + "berserk", + "best", + "better", + "bewildered", + "big", + "billowy", + "bite-sized", + "bitter", + "bizarre", + "black", + "black-and-white", + "bloody", + "blue", + "blue-eyed", + "blushing", + "boiling", + "boorish", + "bored", + "boring", + "bouncy", + "boundless", + "brainy", + "brash", + "brave", + "brawny", + "breakable", + "breezy", + "brief", + "bright", + "bright", + "broad", + "broken", + "brown", + "bumpy", + "burly", + "bustling", + "busy", + "cagey", + "calculating", + "callous", + "calm", + "capable", + "capricious", + "careful", + "careless", + "caring", + "cautious", + "ceaseless", + "certain", + "changeable", + "charming", + "cheap", + "cheerful", + "chemical", + "chief", + "childlike", + "chilly", + "chivalrous", + "chubby", + "chunky", + "clammy", + "classy", + "clean", + "clear", + "clever", + "cloistered", + "cloudy", + "closed", + "clumsy", + "cluttered", + "coherent", + "cold", + "colorful", + "colossal", + "combative", + "comfortable", + "common", + "complete", + "complex", + "concerned", + "condemned", + "confused", + "conscious", + "cooing", + "cool", + "cooperative", + "coordinated", + "courageous", + "cowardly", + "crabby", + "craven", + "crazy", + "creepy", + "crooked", + "crowded", + "cruel", + "cuddly", + "cultured", + "cumbersome", + "curious", + "curly", + "curved", + "curvy", + "cut", + "cute", + "cute", + "cynical", + "daffy", + "daily", + "damaged", + "damaging", + "damp", + "dangerous", + "dapper", + "dark", + "dashing", + "dazzling", + "dead", + "deadpan", + "deafening", + "dear", + "debonair", + "decisive", + "decorous", + "deep", + "deeply", + "defeated", + "defective", + "defiant", + "delicate", + "delicious", + "delightful", + "demonic", + "delirious", + "dependent", + "depressed", + "deranged", + "descriptive", + "deserted", + "detailed", + "determined", + "devilish", + "didactic", + "different", + "difficult", + "diligent", + "direful", + "dirty", + "disagreeable", + "disastrous", + "discreet", + "disgusted", + "disgusting", + "disillusioned", + "dispensable", + "distinct", + "disturbed", + "divergent", + "dizzy", + "domineering", + "doubtful", + "drab", + "draconian", + "dramatic", + "dreary", + "drunk", + "dry", + "dull", + "dusty", + "dynamic", + "dysfunctional", + "eager", + "early", + "earsplitting", + "earthy", + "easy", + "eatable", + "economic", + "educated", + "efficacious", + "efficient", + "eight", + "elastic", + "elated", + "elderly", + "electric", + "elegant", + "elfin", + "elite", + "embarrassed", + "eminent", + "empty", + "enchanted", + "enchanting", + "encouraging", + "endurable", + "energetic", + "enormous", + "entertaining", + "enthusiastic", + "envious", + "equable", + "equal", + "erect", + "erratic", + "ethereal", + "evanescent", + "evasive", + "even", + "excellent", + "excited", + "exciting", + "exclusive", + "exotic", + "expensive", + "extra-large", + "extra-small", + "exuberant", + "exultant", + "fabulous", + "faded", + "faint", + "fair", + "faithful", + "fallacious", + "false", + "familiar", + "famous", + "fanatical", + "fancy", + "fantastic", + "far", + "far-flung", + "fascinated", + "fast", + "fat", + "faulty", + "fearful", + "fearless", + "feeble", + "feigned", + "female", + "fertile", + "festive", + "few", + "fierce", + "filthy", + "fine", + "finicky", + "first", + "five", + "fixed", + "flagrant", + "flaky", + "flashy", + "flat", + "flawless", + "flimsy", + "flippant", + "flowery", + "fluffy", + "fluttering", + "foamy", + "foolish", + "foregoing", + "forgetful", + "fortunate", + "four", + "frail", + "fragile", + "frantic", + "free", + "freezing", + "frequent", + "fresh", + "fretful", + "friendly", + "frightened", + "frightening", + "full", + "fumbling", + "functional", + "funny", + "furry", + "furtive", + "future", + "futuristic", + "fuzzy", + "gabby", + "gainful", + "gamy", + "gaping", + "garrulous", + "gaudy", + "general", + "gentle", + "giant", + "giddy", + "gifted", + "gigantic", + "glamorous", + "gleaming", + "glib", + "glistening", + "glorious", + "glossy", + "godly", + "good", + "goofy", + "gorgeous", + "graceful", + "grandiose", + "grateful", + "gratis", + "gray", + "greasy", + "great", + "greedy", + "green", + "grey", + "grieving", + "groovy", + "grotesque", + "grouchy", + "grubby", + "gruesome", + "grumpy", + "guarded", + "guiltless", + "gullible", + "gusty", + "guttural", + "habitual", + "half", + "hallowed", + "halting", + "handsome", + "handsomely", + "handy", + "hanging", + "hapless", + "happy", + "hard", + "hard-to-find", + "harmonious", + "harsh", + "hateful", + "heady", + "healthy", + "heartbreaking", + "heavenly", + "heavy", + "hellish", + "helpful", + "helpless", + "hesitant", + "hideous", + "high", + "highfalutin", + "high-pitched", + "hilarious", + "hissing", + "historical", + "holistic", + "hollow", + "homeless", + "homely", + "honorable", + "horrible", + "hospitable", + "hot", + "huge", + "hulking", + "humdrum", + "humorous", + "hungry", + "hurried", + "hurt", + "hushed", + "husky", + "hypnotic", + "hysterical", + "icky", + "icy", + "idiotic", + "ignorant", + "ill", + "illegal", + "ill-fated", + "ill-informed", + "illustrious", + "imaginary", + "immense", + "imminent", + "impartial", + "imperfect", + "impolite", + "important", + "imported", + "impossible", + "incandescent", + "incompetent", + "inconclusive", + "industrious", + "incredible", + "inexpensive", + "infamous", + "innate", + "innocent", + "inquisitive", + "insidious", + "instinctive", + "intelligent", + "interesting", + "internal", + "invincible", + "irate", + "irritating", + "itchy", + "jaded", + "jagged", + "jazzy", + "jealous", + "jittery", + "jobless", + "jolly", + "joyous", + "judicious", + "juicy", + "jumbled", + "jumpy", + "juvenile", + "kaput", + "keen", + "kind", + "kindhearted", + "kindly", + "knotty", + "knowing", + "knowledgeable", + "known", + "labored", + "lackadaisical", + "lacking", + "lame", + "lamentable", + "languid", + "large", + "last", + "late", + "laughable", + "lavish", + "lazy", + "lean", + "learned", + "left", + "legal", + "lethal", + "level", + "lewd", + "light", + "like", + "likeable", + "limping", + "literate", + "little", + "lively", + "lively", + "living", + "lonely", + "long", + "longing", + "long-term", + "loose", + "lopsided", + "loud", + "loutish", + "lovely", + "loving", + "low", + "lowly", + "lucky", + "ludicrous", + "lumpy", + "lush", + "luxuriant", + "lying", + "lyrical", + "macabre", + "macho", + "maddening", + "madly", + "magenta", + "magical", + "magnificent", + "majestic", + "makeshift", + "male", + "malicious", + "mammoth", + "maniacal", + "many", + "marked", + "massive", + "married", + "marvelous", + "material", + "materialistic", + "mature", + "mean", + "measly", + "meaty", + "medical", + "meek", + "mellow", + "melodic", + "melted", + "merciful", + "mere", + "messy", + "mighty", + "military", + "milky", + "mindless", + "miniature", + "minor", + "miscreant", + "misty", + "mixed", + "moaning", + "modern", + "moldy", + "momentous", + "motionless", + "mountainous", + "muddled", + "mundane", + "murky", + "mushy", + "mute", + "mysterious", + "naive", + "nappy", + "narrow", + "nasty", + "natural", + "naughty", + "nauseating", + "near", + "neat", + "nebulous", + "necessary", + "needless", + "needy", + "neighborly", + "nervous", + "new", + "next", + "nice", + "nifty", + "nimble", + "nine", + "nippy", + "noiseless", + "noisy", + "nonchalant", + "nondescript", + "nonstop", + "normal", + "nostalgic", + "nosy", + "noxious", + "null", + "numberless", + "numerous", + "nutritious", + "nutty", + "oafish", + "obedient", + "obeisant", + "obese", + "obnoxious", + "obscene", + "obsequious", + "observant", + "obsolete", + "obtainable", + "oceanic", + "odd", + "offbeat", + "old", + "old-fashioned", + "omniscient", + "one", + "onerous", + "open", + "opposite", + "optimal", + "orange", + "ordinary", + "organic", + "ossified", + "outgoing", + "outrageous", + "outstanding", + "oval", + "overconfident", + "overjoyed", + "overrated", + "overt", + "overwrought", + "painful", + "painstaking", + "pale", + "paltry", + "panicky", + "panoramic", + "parallel", + "parched", + "parsimonious", + "past", + "pastoral", + "pathetic", + "peaceful", + "penitent", + "perfect", + "periodic", + "permissible", + "perpetual", + "petite", + "petite", + "phobic", + "physical", + "picayune", + "pink", + "piquant", + "placid", + "plain", + "plant", + "plastic", + "plausible", + "pleasant", + "plucky", + "pointless", + "poised", + "polite", + "political", + "poor", + "possessive", + "possible", + "powerful", + "precious", + "premium", + "present", + "pretty", + "previous", + "pricey", + "prickly", + "private", + "probable", + "productive", + "profuse", + "protective", + "proud", + "psychedelic", + "psychotic", + "public", + "puffy", + "pumped", + "puny", + "purple", + "purring", + "pushy", + "puzzled", + "puzzling", + "quack", + "quaint", + "quarrelsome", + "questionable", + "quick", + "quickest", + "quiet", + "quirky", + "quixotic", + "quizzical", + "rabid", + "racial", + "ragged", + "rainy", + "rambunctious", + "rampant", + "rapid", + "rare", + "raspy", + "ratty", + "ready", + "real", + "rebel", + "receptive", + "recondite", + "red", + "redundant", + "reflective", + "regular", + "relieved", + "remarkable", + "reminiscent", + "repulsive", + "resolute", + "resonant", + "responsible", + "rhetorical", + "rich", + "right", + "righteous", + "rightful", + "rigid", + "ripe", + "ritzy", + "roasted", + "robust", + "romantic", + "roomy", + "rotten", + "rough", + "round", + "royal", + "ruddy", + "rude", + "rural", + "rustic", + "ruthless", + "sable", + "sad", + "safe", + "salty", + "same", + "sassy", + "satisfying", + "savory", + "scandalous", + "scarce", + "scared", + "scary", + "scattered", + "scientific", + "scintillating", + "scrawny", + "screeching", + "second", + "second-hand", + "secret", + "secretive", + "sedate", + "seemly", + "selective", + "selfish", + "separate", + "serious", + "shaggy", + "shaky", + "shallow", + "sharp", + "shiny", + "shivering", + "shocking", + "short", + "shrill", + "shut", + "shy", + "sick", + "silent", + "silent", + "silky", + "silly", + "simple", + "simplistic", + "sincere", + "six", + "skillful", + "skinny", + "sleepy", + "slim", + "slimy", + "slippery", + "sloppy", + "slow", + "small", + "smart", + "smelly", + "smiling", + "smoggy", + "smooth", + "sneaky", + "snobbish", + "snotty", + "soft", + "soggy", + "solid", + "somber", + "sophisticated", + "sordid", + "sore", + "sore", + "sour", + "sparkling", + "special", + "spectacular", + "spicy", + "spiffy", + "spiky", + "spiritual", + "spiteful", + "splendid", + "spooky", + "spotless", + "spotted", + "spotty", + "spurious", + "squalid", + "square", + "squealing", + "squeamish", + "staking", + "stale", + "standing", + "statuesque", + "steadfast", + "steady", + "steep", + "stereotyped", + "sticky", + "stiff", + "stimulating", + "stingy", + "stormy", + "straight", + "strange", + "striped", + "strong", + "stupendous", + "stupid", + "sturdy", + "subdued", + "subsequent", + "substantial", + "successful", + "succinct", + "sudden", + "sulky", + "super", + "superb", + "superficial", + "supreme", + "swanky", + "sweet", + "sweltering", + "swift", + "symptomatic", + "synonymous", + "taboo", + "tacit", + "tacky", + "talented", + "tall", + "tame", + "tan", + "tangible", + "tangy", + "tart", + "tasteful", + "tasteless", + "tasty", + "tawdry", + "tearful", + "tedious", + "teeny", + "teeny-tiny", + "telling", + "temporary", + "ten", + "tender", + "tense", + "tense", + "tenuous", + "terrible", + "terrific", + "tested", + "testy", + "thankful", + "therapeutic", + "thick", + "thin", + "thinkable", + "third", + "thirsty", + "thoughtful", + "thoughtless", + "threatening", + "three", + "thundering", + "tidy", + "tight", + "tightfisted", + "tiny", + "tired", + "tiresome", + "toothsome", + "torpid", + "tough", + "towering", + "tranquil", + "trashy", + "tremendous", + "tricky", + "trite", + "troubled", + "truculent", + "true", + "truthful", + "two", + "typical", + "ubiquitous", + "ugliest", + "ugly", + "ultra", + "unable", + "unaccountable", + "unadvised", + "unarmed", + "unbecoming", + "unbiased", + "uncovered", + "understood", + "undesirable", + "unequal", + "unequaled", + "uneven", + "unhealthy", + "uninterested", + "unique", + "unkempt", + "unknown", + "unnatural", + "unruly", + "unsightly", + "unsuitable", + "untidy", + "unused", + "unusual", + "unwieldy", + "unwritten", + "upbeat", + "uppity", + "upset", + "uptight", + "used", + "useful", + "useless", + "utopian", + "utter", + "uttermost", + "vacuous", + "vagabond", + "vague", + "valuable", + "various", + "vast", + "vengeful", + "venomous", + "verdant", + "versed", + "victorious", + "vigorous", + "violent", + "violet", + "vivacious", + "voiceless", + "volatile", + "voracious", + "vulgar", + "wacky", + "waggish", + "waiting", + "wakeful", + "wandering", + "wanting", + "warlike", + "warm", + "wary", + "wasteful", + "watery", + "weak", + "wealthy", + "weary", + "well-groomed", + "well-made", + "well-off", + "well-to-do", + "wet", + "whimsical", + "whispering", + "white", + "whole", + "wholesale", + "wicked", + "wide", + "wide-eyed", + "wiggly", + "wild", + "willing", + "windy", + "wiry", + "wise", + "wistful", + "witty", + "woebegone", + "womanly", + "wonderful", + "wooden", + "woozy", + "workable", + "worried", + "worthless", + "wrathful", + "wretched", + "wrong", + "wry", + "xenophobic", + "yellow", + "yielding", + "young", + "youthful", + "yummy", + "zany", + "zealous", + "zesty", + "zippy", + "zonked" + ); + public static final List ANIMALS = Arrays.asList( + "Aardvark", + "Albatross", + "Alligator", + "Alpaca", + "Ant", + "Anteater", + "Antelope", + "Ape", + "Armadillo", + "Donkey", + "Baboon", + "Badger", + "Barracuda", + "Bat", + "Bear", + "Beaver", + "Bee", + "Bison", + "Boar", + "Buffalo", + "Butterfly", + "Camel", + "Capybara", + "Caribou", + "Cassowary", + "Cat", + "Caterpillar", + "Cattle", + "Chamois", + "Cheetah", + "Chicken", + "Chimpanzee", + "Chinchilla", + "Chough", + "Clam", + "Cobra", + "Cockroach", + "Cod", + "Cormorant", + "Coyote", + "Crab", + "Crane", + "Crocodile", + "Crow", + "Curlew", + "Deer", + "Dinosaur", + "Dog", + "Dogfish", + "Dolphin", + "Dotterel", + "Dove", + "Dragonfly", + "Duck", + "Dugong", + "Dunlin", + "Eagle", + "Echidna", + "Eel", + "Eland", + "Elephant", + "Elk", + "Emu", + "Falcon", + "Ferret", + "Finch", + "Fish", + "Flamingo", + "Fly", + "Fox", + "Frog", + "Gaur", + "Gazelle", + "Gerbil", + "Giraffe", + "Gnat", + "Gnu", + "Goat", + "Goldfinch", + "Goldfish", + "Goose", + "Gorilla", + "Goshawk", + "Grasshopper", + "Grouse", + "Guanaco", + "Gull", + "Hamster", + "Hare", + "Hawk", + "Hedgehog", + "Heron", + "Herring", + "Hippopotamus", + "Hornet", + "Horse", + "Human", + "Hummingbird", + "Hyena", + "Ibex", + "Ibis", + "Jackal", + "Jaguar", + "Jay", + "Jellyfish", + "Kangaroo", + "Kingfisher", + "Koala", + "Kookabura", + "Kouprey", + "Kudu", + "Lapwing", + "Lark", + "Lemur", + "Leopard", + "Lion", + "Llama", + "Lobster", + "Locust", + "Loris", + "Louse", + "Lyrebird", + "Magpie", + "Mallard", + "Manatee", + "Mandrill", + "Mantis", + "Marten", + "Meerkat", + "Mink", + "Mole", + "Mongoose", + "Monkey", + "Moose", + "Mosquito", + "Mouse", + "Mule", + "Narwhal", + "Newt", + "Nightingale", + "Octopus", + "Okapi", + "Opossum", + "Oryx", + "Ostrich", + "Otter", + "Owl", + "Oyster", + "Panther", + "Parrot", + "Partridge", + "Peafowl", + "Pelican", + "Penguin", + "Pheasant", + "Pig", + "Pigeon", + "Pony", + "Porcupine", + "Porpoise", + "Quail", + "Quelea", + "Quetzal", + "Rabbit", + "Raccoon", + "Rail", + "Ram", + "Rat", + "Raven", + "Reindeer", + "Rhinoceros", + "Rook", + "Salamander", + "Salmon", + "Sandpiper", + "Sardine", + "Scorpion", + "Seahorse", + "Seal", + "Shark", + "Sheep", + "Shrew", + "Skunk", + "Snail", + "Snake", + "Sparrow", + "Spider", + "Spoonbill", + "Squid", + "Squirrel", + "Starling", + "Stingray", + "Stinkbug", + "Stork", + "Swallow", + "Swan", + "Tapir", + "Tarsier", + "Termite", + "Tiger", + "Toad", + "Trout", + "Turkey", + "Turtle", + "Viper", + "Vulture", + "Wallaby", + "Walrus", + "Wasp", + "Weasel", + "Whale", + "Wildcat", + "Wolf", + "Wolverine", + "Wombat", + "Woodcock", + "Woodpecker", + "Worm", + "Wren", + "Yak", + "Zebra" + ); + + public static final List PUBLISH_LANGUAGES = Arrays.asList( + new Language("en", "English", R.string.english), + new Language("zh", "Chinese", R.string.chinese), + new Language("fr", "French", R.string.french), + new Language("de", "German", R.string.german), + new Language("jp", "Japanese", R.string.japanese), + new Language("ru", "Russian", R.string.russian), + new Language("es", "Spanish", R.string.spanish), + new Language("id", "Indonesian", R.string.indonesian), + new Language("it", "Italian", R.string.italian), + new Language("nl", "Dutch", R.string.dutch), + new Language("tr", "Turkish", R.string.turkish), + new Language("pl", "Polish", R.string.polish), + new Language("ms", "Malay", R.string.malay), + new Language("pt", "Portuguese", R.string.portuguese), + new Language("vi", "Vietnamese", R.string.vietnamese), + new Language("th", "Thai", R.string.thai), + new Language("ar", "Arabic", R.string.arabic), + new Language("cs", "Czech", R.string.czech), + new Language("hr", "Croatian", R.string.croatian), + new Language("km", "Cambodian", R.string.cambodian), + new Language("ko", "Korean", R.string.korean), + new Language("no", "Norwegian", R.string.norwegian), + new Language("ro", "Romanian", R.string.romanian), + new Language("hi", "Hindi", R.string.hindi), + new Language("el", "Greek", R.string.greek) + ); + + public static final String LICENSE_COPYRIGHTED = "Copyrighted"; + public static final String LICENSE_OTHER = "Other"; + public static final List LICENSES = Arrays.asList( + new License("None", R.string.none), + new License("Public Domain", R.string.public_domain), + new License("Creative Commons Attribution 4.0 International", "https://creativecommons.org/licenses/by/4.0/legalcode", R.string.cca_4_0_international), + new License("Creative Commons Attribution-ShareAlike 4.0 International", "https://creativecommons.org/licenses/by-sa/4.0/legalcode", R.string.cca_sa_4_0_international), + new License("Creative Commons Attribution-NoDerivatives 4.0 International", "https://creativecommons.org/licenses/by-nd/4.0/legalcode", R.string.cca_nd_4_0_international), + new License("Creative Commons Attribution-NonCommercial 4.0 International", "https://creativecommons.org/licenses/by-nc/4.0/legalcode", R.string.cca_nc_4_0_international), + new License("Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International", "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode", R.string.cca_nc_sa_4_0_international), + new License("Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International", "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode", R.string.cca_nc_nd_4_0_international), + new License(LICENSE_COPYRIGHTED, R.string.copyrighted), + new License(LICENSE_OTHER, R.string.other) + ); +} diff --git a/app/src/main/res/drawable-anydpi/ic_about.xml b/app/src/main/res/drawable-anydpi/ic_about.xml new file mode 100644 index 00000000..596d6b73 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_about.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_add.xml b/app/src/main/res/drawable-anydpi/ic_add.xml new file mode 100644 index 00000000..fa797053 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_add.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_arrow_dropdown.xml b/app/src/main/res/drawable-anydpi/ic_arrow_dropdown.xml new file mode 100644 index 00000000..d7c4a022 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_arrow_dropdown.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_arrow_dropup.xml b/app/src/main/res/drawable-anydpi/ic_arrow_dropup.xml new file mode 100644 index 00000000..16f39fa3 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_arrow_dropup.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_arrow_left.xml b/app/src/main/res/drawable-anydpi/ic_arrow_left.xml new file mode 100644 index 00000000..b94a76b7 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_arrow_left.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_arrow_right.xml b/app/src/main/res/drawable-anydpi/ic_arrow_right.xml new file mode 100644 index 00000000..ac8898d5 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_arrow_right.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_cast.xml b/app/src/main/res/drawable-anydpi/ic_cast.xml new file mode 100644 index 00000000..da0e952f --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_cast.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_cast_connected.xml b/app/src/main/res/drawable-anydpi/ic_cast_connected.xml new file mode 100644 index 00000000..491bf6c8 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_cast_connected.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_check.xml b/app/src/main/res/drawable-anydpi/ic_check.xml new file mode 100644 index 00000000..eb477701 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_check.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_check_circle.xml b/app/src/main/res/drawable-anydpi/ic_check_circle.xml new file mode 100644 index 00000000..bbdc3744 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_check_circle.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_close.xml b/app/src/main/res/drawable-anydpi/ic_close.xml new file mode 100644 index 00000000..443f138a --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_close.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_copy.xml b/app/src/main/res/drawable-anydpi/ic_copy.xml new file mode 100644 index 00000000..b11c7d4e --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_copy.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_delete.xml b/app/src/main/res/drawable-anydpi/ic_delete.xml new file mode 100644 index 00000000..24678bc6 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_delete.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_download.xml b/app/src/main/res/drawable-anydpi/ic_download.xml new file mode 100644 index 00000000..4c5fde21 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_download.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_edit.xml b/app/src/main/res/drawable-anydpi/ic_edit.xml new file mode 100644 index 00000000..f13ba712 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_edit.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_editors_choice.xml b/app/src/main/res/drawable-anydpi/ic_editors_choice.xml new file mode 100644 index 00000000..dd3c6372 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_editors_choice.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_file.xml b/app/src/main/res/drawable-anydpi/ic_file.xml new file mode 100644 index 00000000..c2db6b21 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_file.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_following.xml b/app/src/main/res/drawable-anydpi/ic_following.xml new file mode 100644 index 00000000..22ed2aad --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_following.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_forward_10.xml b/app/src/main/res/drawable-anydpi/ic_forward_10.xml new file mode 100644 index 00000000..55e7a754 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_forward_10.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_fullscreen.xml b/app/src/main/res/drawable-anydpi/ic_fullscreen.xml new file mode 100644 index 00000000..b8f99968 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_fullscreen.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_fullscreen_exit.xml b/app/src/main/res/drawable-anydpi/ic_fullscreen_exit.xml new file mode 100644 index 00000000..f8116a2e --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_fullscreen_exit.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_invites.xml b/app/src/main/res/drawable-anydpi/ic_invites.xml new file mode 100644 index 00000000..4bc8e0a7 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_invites.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_library.xml b/app/src/main/res/drawable-anydpi/ic_library.xml new file mode 100644 index 00000000..4c5fde21 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_library.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_lock.xml b/app/src/main/res/drawable-anydpi/ic_lock.xml new file mode 100644 index 00000000..b6145be4 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_lock.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_new.xml b/app/src/main/res/drawable-anydpi/ic_new.xml new file mode 100644 index 00000000..11109de3 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_new.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_pause.xml b/app/src/main/res/drawable-anydpi/ic_pause.xml new file mode 100644 index 00000000..06af6a1e --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_pause.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_photo.xml b/app/src/main/res/drawable-anydpi/ic_photo.xml new file mode 100644 index 00000000..e2343761 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_photo.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable-anydpi/ic_play.xml b/app/src/main/res/drawable-anydpi/ic_play.xml new file mode 100644 index 00000000..420941aa --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_play.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_publish.xml b/app/src/main/res/drawable-anydpi/ic_publish.xml new file mode 100644 index 00000000..faebc4a5 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_publish.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_publishes.xml b/app/src/main/res/drawable-anydpi/ic_publishes.xml new file mode 100644 index 00000000..dcb5ebd6 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_publishes.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_record.xml b/app/src/main/res/drawable-anydpi/ic_record.xml new file mode 100644 index 00000000..42f62d58 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_record.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_replay_10.xml b/app/src/main/res/drawable-anydpi/ic_replay_10.xml new file mode 100644 index 00000000..a238e2d5 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_replay_10.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_report.xml b/app/src/main/res/drawable-anydpi/ic_report.xml new file mode 100644 index 00000000..1fe6ead6 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_report.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_search.xml b/app/src/main/res/drawable-anydpi/ic_search.xml new file mode 100644 index 00000000..afb0429d --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_search.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_settings.xml b/app/src/main/res/drawable-anydpi/ic_settings.xml new file mode 100644 index 00000000..01b2fba0 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_settings.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_share.xml b/app/src/main/res/drawable-anydpi/ic_share.xml new file mode 100644 index 00000000..ba1ae20a --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_share.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_stop.xml b/app/src/main/res/drawable-anydpi/ic_stop.xml new file mode 100644 index 00000000..34bc28f1 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_stop.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_top.xml b/app/src/main/res/drawable-anydpi/ic_top.xml new file mode 100644 index 00000000..a6bbcd9b --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_top.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_trending.xml b/app/src/main/res/drawable-anydpi/ic_trending.xml new file mode 100644 index 00000000..0ba7252b --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_trending.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_upload.xml b/app/src/main/res/drawable-anydpi/ic_upload.xml new file mode 100644 index 00000000..faebc4a5 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_upload.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_wallet.xml b/app/src/main/res/drawable-anydpi/ic_wallet.xml new file mode 100644 index 00000000..722d0aa4 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_wallet.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-mdpi/src_assets_default_channel_cover.png b/app/src/main/res/drawable-hdpi/default_channel_cover.png similarity index 100% rename from app/src/main/res/drawable-mdpi/src_assets_default_channel_cover.png rename to app/src/main/res/drawable-hdpi/default_channel_cover.png diff --git a/app/src/main/res/drawable-mdpi/src_assets_gerbilhappy.png b/app/src/main/res/drawable-hdpi/gerbil_happy.png similarity index 100% rename from app/src/main/res/drawable-mdpi/src_assets_gerbilhappy.png rename to app/src/main/res/drawable-hdpi/gerbil_happy.png diff --git a/app/src/main/res/drawable-hdpi/gerbil_sad.png b/app/src/main/res/drawable-hdpi/gerbil_sad.png new file mode 100644 index 00000000..153d4adf Binary files /dev/null and b/app/src/main/res/drawable-hdpi/gerbil_sad.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_about.png b/app/src/main/res/drawable-hdpi/ic_about.png new file mode 100644 index 00000000..8613df07 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_about.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_add.png b/app/src/main/res/drawable-hdpi/ic_add.png new file mode 100644 index 00000000..2b125f5f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_dropdown.png b/app/src/main/res/drawable-hdpi/ic_arrow_dropdown.png new file mode 100644 index 00000000..f7e23b6d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_arrow_dropdown.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_dropup.png b/app/src/main/res/drawable-hdpi/ic_arrow_dropup.png new file mode 100644 index 00000000..60113caa Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_arrow_dropup.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_left.png b/app/src/main/res/drawable-hdpi/ic_arrow_left.png new file mode 100644 index 00000000..11d1f4b6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_arrow_left.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_right.png b/app/src/main/res/drawable-hdpi/ic_arrow_right.png new file mode 100644 index 00000000..76c1c906 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_arrow_right.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_cast.png b/app/src/main/res/drawable-hdpi/ic_cast.png new file mode 100644 index 00000000..5449b8f5 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_cast.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_cast_connected.png b/app/src/main/res/drawable-hdpi/ic_cast_connected.png new file mode 100644 index 00000000..55bddaad Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_cast_connected.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_check.png b/app/src/main/res/drawable-hdpi/ic_check.png new file mode 100644 index 00000000..92a31879 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_check.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_check_circle.png b/app/src/main/res/drawable-hdpi/ic_check_circle.png new file mode 100644 index 00000000..ff8c711e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_check_circle.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_close.png b/app/src/main/res/drawable-hdpi/ic_close.png new file mode 100644 index 00000000..c661d153 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_close.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_copy.png b/app/src/main/res/drawable-hdpi/ic_copy.png new file mode 100644 index 00000000..7821875d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_copy.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_delete.png b/app/src/main/res/drawable-hdpi/ic_delete.png new file mode 100644 index 00000000..e366b650 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_delete.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_download.png b/app/src/main/res/drawable-hdpi/ic_download.png new file mode 100644 index 00000000..6f4cf5db Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_download.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_edit.png b/app/src/main/res/drawable-hdpi/ic_edit.png new file mode 100644 index 00000000..a3226fb9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_edit.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_editors_choice.png b/app/src/main/res/drawable-hdpi/ic_editors_choice.png new file mode 100644 index 00000000..bc2c85d3 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_editors_choice.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_file.png b/app/src/main/res/drawable-hdpi/ic_file.png new file mode 100644 index 00000000..a2b608dc Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_file.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_following.png b/app/src/main/res/drawable-hdpi/ic_following.png new file mode 100644 index 00000000..f39b95f1 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_following.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_forward_10.png b/app/src/main/res/drawable-hdpi/ic_forward_10.png new file mode 100644 index 00000000..d8763537 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_forward_10.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_fullscreen.png b/app/src/main/res/drawable-hdpi/ic_fullscreen.png new file mode 100644 index 00000000..10decc15 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_fullscreen.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_fullscreen_exit.png b/app/src/main/res/drawable-hdpi/ic_fullscreen_exit.png new file mode 100644 index 00000000..a43ff782 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_fullscreen_exit.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_invites.png b/app/src/main/res/drawable-hdpi/ic_invites.png new file mode 100644 index 00000000..07bab014 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_invites.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png deleted file mode 100644 index 6c7126b0..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_lbry.png b/app/src/main/res/drawable-hdpi/ic_lbry.png deleted file mode 100644 index b23a7bea..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_lbry.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_library.png b/app/src/main/res/drawable-hdpi/ic_library.png new file mode 100644 index 00000000..6f4cf5db Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_library.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_lock.png b/app/src/main/res/drawable-hdpi/ic_lock.png new file mode 100644 index 00000000..b5e83c73 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_lock.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_new.png b/app/src/main/res/drawable-hdpi/ic_new.png new file mode 100644 index 00000000..5e2d353e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_new.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_pause.png b/app/src/main/res/drawable-hdpi/ic_pause.png new file mode 100644 index 00000000..0548a384 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_pause.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_photo.png b/app/src/main/res/drawable-hdpi/ic_photo.png new file mode 100644 index 00000000..b94ccbdb Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_photo.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_play.png b/app/src/main/res/drawable-hdpi/ic_play.png new file mode 100644 index 00000000..556c8729 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_play.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_publish.png b/app/src/main/res/drawable-hdpi/ic_publish.png new file mode 100644 index 00000000..6edf2fd8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_publish.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_publishes.png b/app/src/main/res/drawable-hdpi/ic_publishes.png new file mode 100644 index 00000000..2dec2b6a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_publishes.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_record.png b/app/src/main/res/drawable-hdpi/ic_record.png new file mode 100644 index 00000000..6b81b67b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_record.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_replay_10.png b/app/src/main/res/drawable-hdpi/ic_replay_10.png new file mode 100644 index 00000000..7a487849 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_replay_10.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_report.png b/app/src/main/res/drawable-hdpi/ic_report.png new file mode 100644 index 00000000..48967e15 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_report.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_search.png b/app/src/main/res/drawable-hdpi/ic_search.png new file mode 100644 index 00000000..f9c0afe1 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_search.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_settings.png b/app/src/main/res/drawable-hdpi/ic_settings.png new file mode 100644 index 00000000..a99894b8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_settings.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_share.png b/app/src/main/res/drawable-hdpi/ic_share.png new file mode 100644 index 00000000..6131f566 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_share.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_stop.png b/app/src/main/res/drawable-hdpi/ic_stop.png new file mode 100644 index 00000000..b9164b8b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stop.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_top.png b/app/src/main/res/drawable-hdpi/ic_top.png new file mode 100644 index 00000000..0b8356af Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_top.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_trending.png b/app/src/main/res/drawable-hdpi/ic_trending.png new file mode 100644 index 00000000..546e723a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_trending.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_upload.png b/app/src/main/res/drawable-hdpi/ic_upload.png new file mode 100644 index 00000000..6edf2fd8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_upload.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_wallet.png b/app/src/main/res/drawable-hdpi/ic_wallet.png new file mode 100644 index 00000000..7930303c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_wallet.png differ diff --git a/app/src/main/res/drawable-hdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png b/app/src/main/res/drawable-hdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png deleted file mode 100644 index ad03a63b..00000000 Binary files a/app/src/main/res/drawable-hdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/node_modules_reactnavigation_src_views_assets_backicon.png b/app/src/main/res/drawable-hdpi/node_modules_reactnavigation_src_views_assets_backicon.png deleted file mode 100644 index ad03a63b..00000000 Binary files a/app/src/main/res/drawable-hdpi/node_modules_reactnavigation_src_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png b/app/src/main/res/drawable-hdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png deleted file mode 100644 index ad03a63b..00000000 Binary files a/app/src/main/res/drawable-hdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png b/app/src/main/res/drawable-hdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png deleted file mode 100644 index ad03a63b..00000000 Binary files a/app/src/main/res/drawable-hdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png b/app/src/main/res/drawable-hdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png deleted file mode 100644 index ad03a63b..00000000 Binary files a/app/src/main/res/drawable-hdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/src_assets_stripe.png b/app/src/main/res/drawable-hdpi/stripe_2x.png similarity index 100% rename from app/src/main/res/drawable-xhdpi/src_assets_stripe.png rename to app/src/main/res/drawable-hdpi/stripe_2x.png diff --git a/app/src/main/res/drawable-mdpi/default_channel_cover.png b/app/src/main/res/drawable-mdpi/default_channel_cover.png new file mode 100644 index 00000000..9b49120a Binary files /dev/null and b/app/src/main/res/drawable-mdpi/default_channel_cover.png differ diff --git a/app/src/main/res/drawable-mdpi/gerbil_happy.png b/app/src/main/res/drawable-mdpi/gerbil_happy.png new file mode 100644 index 00000000..4247f831 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/gerbil_happy.png differ diff --git a/app/src/main/res/drawable-mdpi/gerbil_sad.png b/app/src/main/res/drawable-mdpi/gerbil_sad.png new file mode 100644 index 00000000..153d4adf Binary files /dev/null and b/app/src/main/res/drawable-mdpi/gerbil_sad.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_about.png b/app/src/main/res/drawable-mdpi/ic_about.png new file mode 100644 index 00000000..dd2973a3 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_about.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_add.png b/app/src/main/res/drawable-mdpi/ic_add.png new file mode 100644 index 00000000..a69d739e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_dropdown.png b/app/src/main/res/drawable-mdpi/ic_arrow_dropdown.png new file mode 100644 index 00000000..5cd1ef44 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_arrow_dropdown.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_dropup.png b/app/src/main/res/drawable-mdpi/ic_arrow_dropup.png new file mode 100644 index 00000000..aed6fb86 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_arrow_dropup.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_left.png b/app/src/main/res/drawable-mdpi/ic_arrow_left.png new file mode 100644 index 00000000..ceaf25a6 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_arrow_left.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_right.png b/app/src/main/res/drawable-mdpi/ic_arrow_right.png new file mode 100644 index 00000000..3aec31d3 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_arrow_right.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_cast.png b/app/src/main/res/drawable-mdpi/ic_cast.png new file mode 100644 index 00000000..60bb5f50 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_cast.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_cast_connected.png b/app/src/main/res/drawable-mdpi/ic_cast_connected.png new file mode 100644 index 00000000..b4b72ba3 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_cast_connected.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_check.png b/app/src/main/res/drawable-mdpi/ic_check.png new file mode 100644 index 00000000..038f4e35 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_check.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_check_circle.png b/app/src/main/res/drawable-mdpi/ic_check_circle.png new file mode 100644 index 00000000..9545c7f0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_check_circle.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_close.png b/app/src/main/res/drawable-mdpi/ic_close.png new file mode 100644 index 00000000..6060ea25 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_close.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_copy.png b/app/src/main/res/drawable-mdpi/ic_copy.png new file mode 100644 index 00000000..91230913 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_copy.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_delete.png b/app/src/main/res/drawable-mdpi/ic_delete.png new file mode 100644 index 00000000..554abc4c Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_delete.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_download.png b/app/src/main/res/drawable-mdpi/ic_download.png new file mode 100644 index 00000000..03968835 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_download.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_edit.png b/app/src/main/res/drawable-mdpi/ic_edit.png new file mode 100644 index 00000000..f0c1fafa Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_edit.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_editors_choice.png b/app/src/main/res/drawable-mdpi/ic_editors_choice.png new file mode 100644 index 00000000..2a6b26f5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_editors_choice.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_file.png b/app/src/main/res/drawable-mdpi/ic_file.png new file mode 100644 index 00000000..1167c3d5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_file.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_following.png b/app/src/main/res/drawable-mdpi/ic_following.png new file mode 100644 index 00000000..fac666b5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_following.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_forward_10.png b/app/src/main/res/drawable-mdpi/ic_forward_10.png new file mode 100644 index 00000000..751934c3 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_forward_10.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_fullscreen.png b/app/src/main/res/drawable-mdpi/ic_fullscreen.png new file mode 100644 index 00000000..baed2aa2 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_fullscreen.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_fullscreen_exit.png b/app/src/main/res/drawable-mdpi/ic_fullscreen_exit.png new file mode 100644 index 00000000..b4a402c2 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_fullscreen_exit.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_invites.png b/app/src/main/res/drawable-mdpi/ic_invites.png new file mode 100644 index 00000000..72ef5fa2 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_invites.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index 542c83bc..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_lbry.png b/app/src/main/res/drawable-mdpi/ic_lbry.png deleted file mode 100644 index f59cbefa..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_lbry.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_library.png b/app/src/main/res/drawable-mdpi/ic_library.png new file mode 100644 index 00000000..03968835 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_library.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_lock.png b/app/src/main/res/drawable-mdpi/ic_lock.png new file mode 100644 index 00000000..93eabc9f Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_lock.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_new.png b/app/src/main/res/drawable-mdpi/ic_new.png new file mode 100644 index 00000000..0ed37115 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_new.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_pause.png b/app/src/main/res/drawable-mdpi/ic_pause.png new file mode 100644 index 00000000..48cf98d2 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_pause.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_photo.png b/app/src/main/res/drawable-mdpi/ic_photo.png new file mode 100644 index 00000000..668c633d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_photo.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_play.png b/app/src/main/res/drawable-mdpi/ic_play.png new file mode 100644 index 00000000..2b425776 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_play.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_publish.png b/app/src/main/res/drawable-mdpi/ic_publish.png new file mode 100644 index 00000000..c914b144 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_publish.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_publishes.png b/app/src/main/res/drawable-mdpi/ic_publishes.png new file mode 100644 index 00000000..1c07f3dd Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_publishes.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_record.png b/app/src/main/res/drawable-mdpi/ic_record.png new file mode 100644 index 00000000..eb6412b3 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_record.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_replay_10.png b/app/src/main/res/drawable-mdpi/ic_replay_10.png new file mode 100644 index 00000000..d1a44f69 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_replay_10.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_report.png b/app/src/main/res/drawable-mdpi/ic_report.png new file mode 100644 index 00000000..7d8ca772 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_report.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_search.png b/app/src/main/res/drawable-mdpi/ic_search.png new file mode 100644 index 00000000..6fd5a13c Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_search.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_settings.png b/app/src/main/res/drawable-mdpi/ic_settings.png new file mode 100644 index 00000000..69c67dd5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_settings.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_share.png b/app/src/main/res/drawable-mdpi/ic_share.png new file mode 100644 index 00000000..c9e997d0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_share.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_stop.png b/app/src/main/res/drawable-mdpi/ic_stop.png new file mode 100644 index 00000000..307fe3af Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stop.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_top.png b/app/src/main/res/drawable-mdpi/ic_top.png new file mode 100644 index 00000000..50babf24 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_top.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_trending.png b/app/src/main/res/drawable-mdpi/ic_trending.png new file mode 100644 index 00000000..9945e6d0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_trending.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_upload.png b/app/src/main/res/drawable-mdpi/ic_upload.png new file mode 100644 index 00000000..c914b144 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_upload.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_wallet.png b/app/src/main/res/drawable-mdpi/ic_wallet.png new file mode 100644 index 00000000..387d84e7 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_wallet.png differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativecountrypickermodal_lib_assets_images_close.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativecountrypickermodal_lib_assets_images_close.png deleted file mode 100644 index f8a96b2a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativecountrypickermodal_lib_assets_images_close.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativecountrypickermodal_lib_assets_images_closeios.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativecountrypickermodal_lib_assets_images_closeios.png deleted file mode 100644 index e64d614e..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativecountrypickermodal_lib_assets_images_closeios.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativecountrypickermodal_src_androidclose.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativecountrypickermodal_src_androidclose.png deleted file mode 100644 index f8a96b2a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativecountrypickermodal_src_androidclose.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativepasswordstrengthmeter_src_images_eyeinvisible.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativepasswordstrengthmeter_src_images_eyeinvisible.png deleted file mode 100644 index e7d95e0a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativepasswordstrengthmeter_src_images_eyeinvisible.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativepasswordstrengthmeter_src_images_eyevisible.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativepasswordstrengthmeter_src_images_eyevisible.png deleted file mode 100644 index 8588c6f3..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativepasswordstrengthmeter_src_images_eyevisible.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ad.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ad.png deleted file mode 100644 index 886752f6..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ad.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ae.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ae.png deleted file mode 100644 index a253cd2d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ae.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_af.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_af.png deleted file mode 100644 index 6ae08810..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_af.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ag.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ag.png deleted file mode 100644 index ee529d2b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ag.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ai.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ai.png deleted file mode 100644 index a598c566..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ai.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_al.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_al.png deleted file mode 100644 index 4b59dfbd..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_al.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_am.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_am.png deleted file mode 100644 index 41b497a3..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_am.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ao.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ao.png deleted file mode 100644 index f5ff2374..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ao.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ar.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ar.png deleted file mode 100644 index 0b25d9cb..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ar.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_as.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_as.png deleted file mode 100644 index 1b571007..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_as.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_at.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_at.png deleted file mode 100644 index 75646bab..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_at.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_au.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_au.png deleted file mode 100644 index f2572d72..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_au.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_aw.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_aw.png deleted file mode 100644 index a72bfddc..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_aw.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ax.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ax.png deleted file mode 100644 index d78ff2e3..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ax.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_az.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_az.png deleted file mode 100644 index f639aefd..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_az.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ba.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ba.png deleted file mode 100644 index a4ac356d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ba.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bb.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bb.png deleted file mode 100644 index 2bf58e69..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bb.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bd.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bd.png deleted file mode 100644 index e9872d15..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bd.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_be.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_be.png deleted file mode 100644 index 5d1b8325..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_be.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bf.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bf.png deleted file mode 100644 index 5172dbfb..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bf.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bg.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bg.png deleted file mode 100644 index d78308df..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bg.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bh.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bh.png deleted file mode 100644 index 5e247e7a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bh.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bi.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bi.png deleted file mode 100644 index 26186437..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bi.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bj.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bj.png deleted file mode 100644 index 20e281f2..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bj.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bl.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bl.png deleted file mode 100644 index a4938528..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bl.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bm.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bm.png deleted file mode 100644 index 29a8532a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bm.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bn.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bn.png deleted file mode 100644 index b4a3e60e..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bn.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bo.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bo.png deleted file mode 100644 index 342267cb..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bo.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bq.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bq.png deleted file mode 100644 index 0386cc3e..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bq.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_br.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_br.png deleted file mode 100644 index 43725657..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_br.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bs.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bs.png deleted file mode 100644 index 1bbb1d8f..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bs.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bt.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bt.png deleted file mode 100644 index cd4c8539..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bt.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bw.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bw.png deleted file mode 100644 index 555d80b3..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bw.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_by.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_by.png deleted file mode 100644 index 0dc31020..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_by.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bz.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bz.png deleted file mode 100644 index 3b6c39e6..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_bz.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ca.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ca.png deleted file mode 100644 index c939b041..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ca.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cc.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cc.png deleted file mode 100644 index fa52dba8..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cc.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cd.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cd.png deleted file mode 100644 index 44043fac..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cd.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cf.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cf.png deleted file mode 100644 index 5b7cb225..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cf.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cg.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cg.png deleted file mode 100644 index 2d7ce4c0..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cg.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ch.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ch.png deleted file mode 100644 index 5fe151ca..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ch.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ci.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ci.png deleted file mode 100644 index 0534124c..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ci.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ck.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ck.png deleted file mode 100644 index e45cb390..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ck.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cl.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cl.png deleted file mode 100644 index af74ffc9..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cl.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cm.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cm.png deleted file mode 100644 index b33c8115..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cm.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cn.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cn.png deleted file mode 100644 index d31bab71..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cn.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_co.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_co.png deleted file mode 100644 index b6aae55c..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_co.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cr.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cr.png deleted file mode 100644 index 9c92f6de..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cu.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cu.png deleted file mode 100644 index f21090e2..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cu.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cv.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cv.png deleted file mode 100644 index 6eeae62b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cv.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cw.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cw.png deleted file mode 100644 index c38132ce..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cw.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cx.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cx.png deleted file mode 100644 index 9a3e367c..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cx.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cy.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cy.png deleted file mode 100644 index 55446041..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cy.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cz.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cz.png deleted file mode 100644 index 4fc3adb5..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_cz.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_de.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_de.png deleted file mode 100644 index eea2e58b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_de.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dj.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dj.png deleted file mode 100644 index dbc95d77..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dj.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dk.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dk.png deleted file mode 100644 index e3471d34..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dk.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dm.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dm.png deleted file mode 100644 index a158c88f..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dm.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_do.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_do.png deleted file mode 100644 index 81fa5e8b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_do.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dz.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dz.png deleted file mode 100644 index b2768bcc..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_dz.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ec.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ec.png deleted file mode 100644 index 27fe8115..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ec.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ee.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ee.png deleted file mode 100644 index 21b4b72d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ee.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_eg.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_eg.png deleted file mode 100644 index d98e5d3a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_eg.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_eh.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_eh.png deleted file mode 100644 index cf451799..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_eh.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_er.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_er.png deleted file mode 100644 index 3f88fc52..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_er.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_es.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_es.png deleted file mode 100644 index f589a835..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_es.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_et.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_et.png deleted file mode 100644 index d759c2fd..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_et.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fi.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fi.png deleted file mode 100644 index 2bcb6a55..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fi.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fj.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fj.png deleted file mode 100644 index 7aef415f..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fj.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fk.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fk.png deleted file mode 100644 index 7b59f8c1..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fk.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fm.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fm.png deleted file mode 100644 index 1dfbdffe..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fm.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fo.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fo.png deleted file mode 100644 index e2ca151a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fo.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fr.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fr.png deleted file mode 100644 index fcfa7caf..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_fr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ga.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ga.png deleted file mode 100644 index 2dc5f0fc..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ga.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gb.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gb.png deleted file mode 100644 index f1e0e126..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gb.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gd.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gd.png deleted file mode 100644 index 5e3ed13b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gd.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ge.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ge.png deleted file mode 100644 index cd5b75de..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ge.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gf.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gf.png deleted file mode 100644 index fb15b809..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gf.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gg.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gg.png deleted file mode 100644 index 1cf113d4..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gg.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gh.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gh.png deleted file mode 100644 index a7b60ce7..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gh.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gi.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gi.png deleted file mode 100644 index 74fae09c..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gi.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gm.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gm.png deleted file mode 100644 index ca440bb6..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gm.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gn.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gn.png deleted file mode 100644 index 0740a3fc..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gn.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gp.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gp.png deleted file mode 100644 index 3223f1a3..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gq.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gq.png deleted file mode 100644 index bc9c8c46..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gq.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gr.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gr.png deleted file mode 100644 index ec65864a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gt.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gt.png deleted file mode 100644 index 3c7cee7d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gt.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gu.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gu.png deleted file mode 100644 index c7e586f3..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gu.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gw.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gw.png deleted file mode 100644 index 515d457f..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gw.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gy.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gy.png deleted file mode 100644 index 6c3e6733..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_gy.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hk.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hk.png deleted file mode 100644 index d7bbe5a2..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hk.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hn.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hn.png deleted file mode 100644 index ee1d1028..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hn.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hr.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hr.png deleted file mode 100644 index 2dae8a8a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ht.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ht.png deleted file mode 100644 index 2e15f899..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ht.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hu.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hu.png deleted file mode 100644 index c1c028ec..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_hu.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_id.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_id.png deleted file mode 100644 index 619215da..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_id.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ie.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ie.png deleted file mode 100644 index 3881ba34..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ie.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_il.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_il.png deleted file mode 100644 index 33fc90c2..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_il.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_im.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_im.png deleted file mode 100644 index a7a52cfa..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_im.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_in.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_in.png deleted file mode 100644 index 2f06567b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_in.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_io.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_io.png deleted file mode 100644 index 58a4b9be..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_io.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_iq.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_iq.png deleted file mode 100644 index 6b5eb22a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_iq.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ir.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ir.png deleted file mode 100644 index 36f7ec83..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ir.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_is.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_is.png deleted file mode 100644 index 74fef41d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_is.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_it.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_it.png deleted file mode 100644 index ff7ed317..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_it.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_je.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_je.png deleted file mode 100644 index dced1b0b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_je.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_jm.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_jm.png deleted file mode 100644 index 68e58fee..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_jm.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_jo.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_jo.png deleted file mode 100644 index 57bd76a6..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_jo.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_jp.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_jp.png deleted file mode 100644 index 33f3a757..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_jp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ke.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ke.png deleted file mode 100644 index 9e8373fd..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ke.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kg.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kg.png deleted file mode 100644 index 3e7d6611..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kg.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kh.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kh.png deleted file mode 100644 index cf76786b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kh.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ki.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ki.png deleted file mode 100644 index ff8e470d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ki.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_km.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_km.png deleted file mode 100644 index cbd5e1b5..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_km.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kn.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kn.png deleted file mode 100644 index fed64fc0..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kn.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kp.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kp.png deleted file mode 100644 index b25aadc3..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kr.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kr.png deleted file mode 100644 index d035cab9..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ks.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ks.png deleted file mode 100644 index 942e1b58..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ks.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kw.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kw.png deleted file mode 100644 index 8c01668d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kw.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ky.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ky.png deleted file mode 100644 index 80bf785d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ky.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kz.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kz.png deleted file mode 100644 index 436ac8a1..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_kz.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_la.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_la.png deleted file mode 100644 index 87d7fb3c..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_la.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lb.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lb.png deleted file mode 100644 index 7d3659ab..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lb.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lc.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lc.png deleted file mode 100644 index 4bb0487c..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lc.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_li.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_li.png deleted file mode 100644 index b68b433a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_li.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lk.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lk.png deleted file mode 100644 index 15e45c81..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lk.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lr.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lr.png deleted file mode 100644 index 36948eef..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ls.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ls.png deleted file mode 100644 index 70cab723..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ls.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lt.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lt.png deleted file mode 100644 index 80bc5805..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lt.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lu.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lu.png deleted file mode 100644 index c5c2246c..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lu.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lv.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lv.png deleted file mode 100644 index 75431d19..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_lv.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ly.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ly.png deleted file mode 100644 index 2914da29..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ly.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ma.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ma.png deleted file mode 100644 index 0f751a1c..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ma.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mc.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mc.png deleted file mode 100644 index 3f8311b2..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mc.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_md.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_md.png deleted file mode 100644 index 4645ae10..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_md.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_me.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_me.png deleted file mode 100644 index 941d51d4..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_me.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mf.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mf.png deleted file mode 100644 index fcfa7caf..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mf.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mg.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mg.png deleted file mode 100644 index 43922054..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mg.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mh.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mh.png deleted file mode 100644 index 8438bfa3..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mh.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mk.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mk.png deleted file mode 100644 index 3c08615b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mk.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ml.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ml.png deleted file mode 100644 index ce81958a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ml.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mm.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mm.png deleted file mode 100644 index 3c1c0856..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mm.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mn.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mn.png deleted file mode 100644 index 2771b270..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mn.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mo.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mo.png deleted file mode 100644 index 2e62a9d8..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mo.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mp.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mp.png deleted file mode 100644 index 98ce37bd..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mq.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mq.png deleted file mode 100644 index 06466b3b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mq.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mr.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mr.png deleted file mode 100644 index f4dcf1d2..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ms.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ms.png deleted file mode 100644 index 163f5996..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ms.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mt.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mt.png deleted file mode 100644 index 950502ab..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mt.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mu.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mu.png deleted file mode 100644 index a6349637..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mu.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mv.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mv.png deleted file mode 100644 index 565a4083..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mv.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mw.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mw.png deleted file mode 100644 index 442dbc58..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mw.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mx.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mx.png deleted file mode 100644 index 666424d1..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mx.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_my.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_my.png deleted file mode 100644 index 215448cd..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_my.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mz.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mz.png deleted file mode 100644 index 18e2a949..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_mz.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_na.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_na.png deleted file mode 100644 index ca31b5d2..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_na.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nc.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nc.png deleted file mode 100644 index a55d0374..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nc.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ne.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ne.png deleted file mode 100644 index e0097297..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ne.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nf.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nf.png deleted file mode 100644 index 8a83dbf4..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nf.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ng.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ng.png deleted file mode 100644 index ee5775a8..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ng.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ni.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ni.png deleted file mode 100644 index 2ebe882a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ni.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nl.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nl.png deleted file mode 100644 index 0386cc3e..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nl.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_no.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_no.png deleted file mode 100644 index bb2f806b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_no.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_np.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_np.png deleted file mode 100644 index 726500cc..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_np.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nr.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nr.png deleted file mode 100644 index 65b58110..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nu.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nu.png deleted file mode 100644 index 4bc2ad23..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nu.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nz.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nz.png deleted file mode 100644 index abe4acf6..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_nz.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_om.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_om.png deleted file mode 100644 index 86812676..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_om.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pa.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pa.png deleted file mode 100644 index e821dee8..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pa.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pe.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pe.png deleted file mode 100644 index 5af51ad7..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pe.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pf.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pf.png deleted file mode 100644 index 4ecb31d9..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pf.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pg.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pg.png deleted file mode 100644 index 14818457..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pg.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ph.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ph.png deleted file mode 100644 index ffa33a92..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ph.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pk.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pk.png deleted file mode 100644 index 645971c5..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pk.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pl.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pl.png deleted file mode 100644 index 9d4e6925..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pl.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pm.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pm.png deleted file mode 100644 index 336cb210..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pm.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pr.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pr.png deleted file mode 100644 index 3fc7a074..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ps.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ps.png deleted file mode 100644 index ffc7621a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ps.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pt.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pt.png deleted file mode 100644 index 6526f8c1..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pt.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pw.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pw.png deleted file mode 100644 index 0a91ea56..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_pw.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_py.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_py.png deleted file mode 100644 index 40dffa49..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_py.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_qa.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_qa.png deleted file mode 100644 index 9cf00683..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_qa.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_re.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_re.png deleted file mode 100644 index 1dc648e7..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_re.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ro.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ro.png deleted file mode 100644 index 0bee8d1a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ro.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_rs.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_rs.png deleted file mode 100644 index 19fd38a6..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_rs.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ru.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ru.png deleted file mode 100644 index 66741a4d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ru.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_rw.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_rw.png deleted file mode 100644 index 24080d6d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_rw.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sa.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sa.png deleted file mode 100644 index 66dadb5b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sa.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sb.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sb.png deleted file mode 100644 index 97e0fc7c..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sb.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sc.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sc.png deleted file mode 100644 index 76863735..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sc.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sd.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sd.png deleted file mode 100644 index 9a6f886d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sd.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_se.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_se.png deleted file mode 100644 index 59595199..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_se.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sg.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sg.png deleted file mode 100644 index 8ba42209..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sg.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sh.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sh.png deleted file mode 100644 index d4c97406..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sh.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_si.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_si.png deleted file mode 100644 index 3b751344..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_si.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sj.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sj.png deleted file mode 100644 index bb2f806b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sj.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sk.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sk.png deleted file mode 100644 index 0769397a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sk.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sl.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sl.png deleted file mode 100644 index 96cddd4f..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sl.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sm.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sm.png deleted file mode 100644 index 4ee071c2..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sm.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sn.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sn.png deleted file mode 100644 index 9415c60e..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sn.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_so.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_so.png deleted file mode 100644 index 93a7fdc9..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_so.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sr.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sr.png deleted file mode 100644 index 47092d9e..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ss.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ss.png deleted file mode 100644 index e5f2259d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ss.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_st.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_st.png deleted file mode 100644 index 85f7d386..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_st.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sv.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sv.png deleted file mode 100644 index 97795729..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sv.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sx.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sx.png deleted file mode 100644 index ec17c244..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sx.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sy.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sy.png deleted file mode 100644 index a80b6b11..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sy.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sz.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sz.png deleted file mode 100644 index 89337677..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_sz.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tc.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tc.png deleted file mode 100644 index 5f5c2449..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tc.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_td.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_td.png deleted file mode 100644 index 41f123b5..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_td.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tg.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tg.png deleted file mode 100644 index a4a1d9f9..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tg.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_th.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_th.png deleted file mode 100644 index f0f7207d..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_th.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tj.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tj.png deleted file mode 100644 index 682b5e0f..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tj.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tk.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tk.png deleted file mode 100644 index 24b93302..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tk.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tl.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tl.png deleted file mode 100644 index 8a98e900..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tl.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tm.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tm.png deleted file mode 100644 index 58567c81..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tm.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tn.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tn.png deleted file mode 100644 index db4951a6..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tn.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_to.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_to.png deleted file mode 100644 index 95b78ce2..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_to.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tr.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tr.png deleted file mode 100644 index 95d0c871..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tr.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tt.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tt.png deleted file mode 100644 index 39a4af42..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tt.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tv.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tv.png deleted file mode 100644 index 6bfe412e..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tv.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tw.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tw.png deleted file mode 100644 index 80e07d81..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tw.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tz.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tz.png deleted file mode 100644 index 446ecb4f..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_tz.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ua.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ua.png deleted file mode 100644 index 00234794..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ua.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ug.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ug.png deleted file mode 100644 index cdcab6a1..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ug.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_us.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_us.png deleted file mode 100644 index 5b96ff24..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_us.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_uy.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_uy.png deleted file mode 100644 index 219ef44a..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_uy.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_uz.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_uz.png deleted file mode 100644 index 80e0a446..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_uz.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_va.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_va.png deleted file mode 100644 index c94c81dd..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_va.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vc.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vc.png deleted file mode 100644 index 77196ed9..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vc.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ve.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ve.png deleted file mode 100644 index 40ae68eb..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ve.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vg.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vg.png deleted file mode 100644 index 4de2078b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vg.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vi.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vi.png deleted file mode 100644 index a4bd67cb..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vi.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vn.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vn.png deleted file mode 100644 index d6838523..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vn.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vu.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vu.png deleted file mode 100644 index e1ad764e..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_vu.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_wf.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_wf.png deleted file mode 100644 index c3c5a9e8..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_wf.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ws.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ws.png deleted file mode 100644 index 71db01fa..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ws.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ye.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ye.png deleted file mode 100644 index 3a2e0a2b..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_ye.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_yt.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_yt.png deleted file mode 100644 index fcfa7caf..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_yt.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_za.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_za.png deleted file mode 100644 index 535fe710..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_za.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_zm.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_zm.png deleted file mode 100644 index 7b0246a8..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_zm.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_zw.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_zw.png deleted file mode 100644 index abf13869..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativephoneinput_lib_resources_flags_images_zw.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnativespeedometer_images_speedometerneedle.png b/app/src/main/res/drawable-mdpi/node_modules_reactnativespeedometer_images_speedometerneedle.png deleted file mode 100644 index 42373f85..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnativespeedometer_images_speedometerneedle.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png b/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png deleted file mode 100644 index 083db295..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backiconmask.png b/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backiconmask.png deleted file mode 100644 index dbddbdff..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backiconmask.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_src_views_assets_backicon.png b/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_src_views_assets_backicon.png deleted file mode 100644 index 083db295..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_src_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_src_views_assets_backiconmask.png b/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_src_views_assets_backiconmask.png deleted file mode 100644 index 5d7df0c0..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_src_views_assets_backiconmask.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png b/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png deleted file mode 100644 index 083db295..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_dist_views_assets_backiconmask.png b/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_dist_views_assets_backiconmask.png deleted file mode 100644 index 5fa299b7..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_dist_views_assets_backiconmask.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png b/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png deleted file mode 100644 index 083db295..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_lib_module_views_assets_backiconmask.png b/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_lib_module_views_assets_backiconmask.png deleted file mode 100644 index dbddbdff..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_lib_module_views_assets_backiconmask.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png b/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png deleted file mode 100644 index 083db295..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_src_views_assets_backiconmask.png b/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_src_views_assets_backiconmask.png deleted file mode 100644 index dbddbdff..00000000 Binary files a/app/src/main/res/drawable-mdpi/node_modules_reactnavigationstack_src_views_assets_backiconmask.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/src_assets_default_avatar.jpg b/app/src/main/res/drawable-mdpi/src_assets_default_avatar.jpg deleted file mode 100644 index 2cffd3af..00000000 Binary files a/app/src/main/res/drawable-mdpi/src_assets_default_avatar.jpg and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/stripe_2x.png b/app/src/main/res/drawable-mdpi/stripe_2x.png new file mode 100644 index 00000000..e377b672 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/stripe_2x.png differ diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/default_channel_cover.png b/app/src/main/res/drawable-xhdpi/default_channel_cover.png new file mode 100644 index 00000000..9b49120a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/default_channel_cover.png differ diff --git a/app/src/main/res/drawable-xhdpi/gerbil_happy.png b/app/src/main/res/drawable-xhdpi/gerbil_happy.png new file mode 100644 index 00000000..4247f831 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/gerbil_happy.png differ diff --git a/app/src/main/res/drawable-xhdpi/gerbil_sad.png b/app/src/main/res/drawable-xhdpi/gerbil_sad.png new file mode 100644 index 00000000..153d4adf Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/gerbil_sad.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_about.png b/app/src/main/res/drawable-xhdpi/ic_about.png new file mode 100644 index 00000000..6729f52e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_about.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_add.png b/app/src/main/res/drawable-xhdpi/ic_add.png new file mode 100644 index 00000000..4423088d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_dropdown.png b/app/src/main/res/drawable-xhdpi/ic_arrow_dropdown.png new file mode 100644 index 00000000..979ee5d4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_arrow_dropdown.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_dropup.png b/app/src/main/res/drawable-xhdpi/ic_arrow_dropup.png new file mode 100644 index 00000000..0ea7d6db Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_arrow_dropup.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_left.png b/app/src/main/res/drawable-xhdpi/ic_arrow_left.png new file mode 100644 index 00000000..7217af6e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_arrow_left.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_right.png b/app/src/main/res/drawable-xhdpi/ic_arrow_right.png new file mode 100644 index 00000000..714857e2 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_arrow_right.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_cast.png b/app/src/main/res/drawable-xhdpi/ic_cast.png new file mode 100644 index 00000000..fdde81a4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_cast.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_cast_connected.png b/app/src/main/res/drawable-xhdpi/ic_cast_connected.png new file mode 100644 index 00000000..bcc195dc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_cast_connected.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_check.png b/app/src/main/res/drawable-xhdpi/ic_check.png new file mode 100644 index 00000000..d9b2249a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_check.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_check_circle.png b/app/src/main/res/drawable-xhdpi/ic_check_circle.png new file mode 100644 index 00000000..1b7f7109 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_check_circle.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_close.png b/app/src/main/res/drawable-xhdpi/ic_close.png new file mode 100644 index 00000000..91f6c7a8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_close.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_copy.png b/app/src/main/res/drawable-xhdpi/ic_copy.png new file mode 100644 index 00000000..a15eb3da Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_copy.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_delete.png b/app/src/main/res/drawable-xhdpi/ic_delete.png new file mode 100644 index 00000000..5eede98d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_delete.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_download.png b/app/src/main/res/drawable-xhdpi/ic_download.png new file mode 100644 index 00000000..e311de88 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_download.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_edit.png b/app/src/main/res/drawable-xhdpi/ic_edit.png new file mode 100644 index 00000000..b508285a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_edit.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_editors_choice.png b/app/src/main/res/drawable-xhdpi/ic_editors_choice.png new file mode 100644 index 00000000..a955fba4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_editors_choice.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_file.png b/app/src/main/res/drawable-xhdpi/ic_file.png new file mode 100644 index 00000000..15c258b0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_file.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_following.png b/app/src/main/res/drawable-xhdpi/ic_following.png new file mode 100644 index 00000000..8209422e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_following.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_forward_10.png b/app/src/main/res/drawable-xhdpi/ic_forward_10.png new file mode 100644 index 00000000..c5acd34f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_forward_10.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_fullscreen.png b/app/src/main/res/drawable-xhdpi/ic_fullscreen.png new file mode 100644 index 00000000..7f3b8676 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_fullscreen.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_fullscreen_exit.png b/app/src/main/res/drawable-xhdpi/ic_fullscreen_exit.png new file mode 100644 index 00000000..9c052625 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_fullscreen_exit.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_invites.png b/app/src/main/res/drawable-xhdpi/ic_invites.png new file mode 100644 index 00000000..075db8d0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_invites.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index df80b81c..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_lbry.png b/app/src/main/res/drawable-xhdpi/ic_lbry.png deleted file mode 100644 index 872021ae..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_lbry.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_library.png b/app/src/main/res/drawable-xhdpi/ic_library.png new file mode 100644 index 00000000..e311de88 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_library.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_lock.png b/app/src/main/res/drawable-xhdpi/ic_lock.png new file mode 100644 index 00000000..ecc037ca Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_lock.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_new.png b/app/src/main/res/drawable-xhdpi/ic_new.png new file mode 100644 index 00000000..9507ba46 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_new.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_pause.png b/app/src/main/res/drawable-xhdpi/ic_pause.png new file mode 100644 index 00000000..192399d1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_pause.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_photo.png b/app/src/main/res/drawable-xhdpi/ic_photo.png new file mode 100644 index 00000000..5165e580 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_photo.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play.png b/app/src/main/res/drawable-xhdpi/ic_play.png new file mode 100644 index 00000000..7770ab21 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_publish.png b/app/src/main/res/drawable-xhdpi/ic_publish.png new file mode 100644 index 00000000..904b34ea Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_publish.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_publishes.png b/app/src/main/res/drawable-xhdpi/ic_publishes.png new file mode 100644 index 00000000..3d88ad33 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_publishes.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_record.png b/app/src/main/res/drawable-xhdpi/ic_record.png new file mode 100644 index 00000000..f8c501cd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_record.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_replay_10.png b/app/src/main/res/drawable-xhdpi/ic_replay_10.png new file mode 100644 index 00000000..14819ffa Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_replay_10.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_report.png b/app/src/main/res/drawable-xhdpi/ic_report.png new file mode 100644 index 00000000..841d4ae2 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_report.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_search.png b/app/src/main/res/drawable-xhdpi/ic_search.png new file mode 100644 index 00000000..aad212cc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_search.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_settings.png b/app/src/main/res/drawable-xhdpi/ic_settings.png new file mode 100644 index 00000000..3d88a9ea Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_settings.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_share.png b/app/src/main/res/drawable-xhdpi/ic_share.png new file mode 100644 index 00000000..c77d4eb3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_share.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stop.png b/app/src/main/res/drawable-xhdpi/ic_stop.png new file mode 100644 index 00000000..3db50c54 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stop.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_top.png b/app/src/main/res/drawable-xhdpi/ic_top.png new file mode 100644 index 00000000..8789cd5f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_top.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_trending.png b/app/src/main/res/drawable-xhdpi/ic_trending.png new file mode 100644 index 00000000..e7e6274b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_trending.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_upload.png b/app/src/main/res/drawable-xhdpi/ic_upload.png new file mode 100644 index 00000000..904b34ea Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_upload.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_wallet.png b/app/src/main/res/drawable-xhdpi/ic_wallet.png new file mode 100644 index 00000000..69eba060 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_wallet.png differ diff --git a/app/src/main/res/drawable-xhdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png b/app/src/main/res/drawable-xhdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png deleted file mode 100644 index 6de0a1cb..00000000 Binary files a/app/src/main/res/drawable-xhdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/node_modules_reactnavigation_src_views_assets_backicon.png b/app/src/main/res/drawable-xhdpi/node_modules_reactnavigation_src_views_assets_backicon.png deleted file mode 100644 index 6de0a1cb..00000000 Binary files a/app/src/main/res/drawable-xhdpi/node_modules_reactnavigation_src_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png b/app/src/main/res/drawable-xhdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png deleted file mode 100644 index 6de0a1cb..00000000 Binary files a/app/src/main/res/drawable-xhdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png b/app/src/main/res/drawable-xhdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png deleted file mode 100644 index 6de0a1cb..00000000 Binary files a/app/src/main/res/drawable-xhdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png b/app/src/main/res/drawable-xhdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png deleted file mode 100644 index 6de0a1cb..00000000 Binary files a/app/src/main/res/drawable-xhdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/stripe_2x.png b/app/src/main/res/drawable-xhdpi/stripe_2x.png new file mode 100644 index 00000000..e377b672 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/stripe_2x.png differ diff --git a/app/src/main/res/drawable-xxhdpi/default_channel_cover.png b/app/src/main/res/drawable-xxhdpi/default_channel_cover.png new file mode 100644 index 00000000..9b49120a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/default_channel_cover.png differ diff --git a/app/src/main/res/drawable-xxhdpi/gerbil_happy.png b/app/src/main/res/drawable-xxhdpi/gerbil_happy.png new file mode 100644 index 00000000..4247f831 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/gerbil_happy.png differ diff --git a/app/src/main/res/drawable-xxhdpi/gerbil_sad.png b/app/src/main/res/drawable-xxhdpi/gerbil_sad.png new file mode 100644 index 00000000..153d4adf Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/gerbil_sad.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_about.png b/app/src/main/res/drawable-xxhdpi/ic_about.png new file mode 100644 index 00000000..85336c2d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_about.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_add.png b/app/src/main/res/drawable-xxhdpi/ic_add.png new file mode 100644 index 00000000..94b04c8f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_dropdown.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_dropdown.png new file mode 100644 index 00000000..2a7e5766 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_dropdown.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_dropup.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_dropup.png new file mode 100644 index 00000000..8c868014 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_dropup.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_left.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_left.png new file mode 100644 index 00000000..6220d1ea Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_left.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_right.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_right.png new file mode 100644 index 00000000..b6b817dd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_right.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_cast.png b/app/src/main/res/drawable-xxhdpi/ic_cast.png new file mode 100644 index 00000000..62fc53f7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_cast.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_cast_connected.png b/app/src/main/res/drawable-xxhdpi/ic_cast_connected.png new file mode 100644 index 00000000..a28df6de Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_cast_connected.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_check.png b/app/src/main/res/drawable-xxhdpi/ic_check.png new file mode 100644 index 00000000..506b931d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_check.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_check_circle.png b/app/src/main/res/drawable-xxhdpi/ic_check_circle.png new file mode 100644 index 00000000..eaf7a66e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_check_circle.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_close.png b/app/src/main/res/drawable-xxhdpi/ic_close.png new file mode 100644 index 00000000..1e41c557 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_close.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_copy.png b/app/src/main/res/drawable-xxhdpi/ic_copy.png new file mode 100644 index 00000000..bfc0c258 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_copy.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_delete.png b/app/src/main/res/drawable-xxhdpi/ic_delete.png new file mode 100644 index 00000000..df20cf5d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_delete.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_download.png b/app/src/main/res/drawable-xxhdpi/ic_download.png new file mode 100644 index 00000000..f0df0523 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_download.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_edit.png b/app/src/main/res/drawable-xxhdpi/ic_edit.png new file mode 100644 index 00000000..40ef06e3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_edit.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_editors_choice.png b/app/src/main/res/drawable-xxhdpi/ic_editors_choice.png new file mode 100644 index 00000000..bbf3481a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_editors_choice.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_file.png b/app/src/main/res/drawable-xxhdpi/ic_file.png new file mode 100644 index 00000000..be649527 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_file.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_following.png b/app/src/main/res/drawable-xxhdpi/ic_following.png new file mode 100644 index 00000000..b7a0f318 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_following.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_forward_10.png b/app/src/main/res/drawable-xxhdpi/ic_forward_10.png new file mode 100644 index 00000000..ac59544c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_forward_10.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fullscreen.png b/app/src/main/res/drawable-xxhdpi/ic_fullscreen.png new file mode 100644 index 00000000..ff166dd0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fullscreen.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fullscreen_exit.png b/app/src/main/res/drawable-xxhdpi/ic_fullscreen_exit.png new file mode 100644 index 00000000..60c521bb Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fullscreen_exit.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_invites.png b/app/src/main/res/drawable-xxhdpi/ic_invites.png new file mode 100644 index 00000000..5224ca25 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_invites.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png deleted file mode 100644 index d5c9438b..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_lbry.png b/app/src/main/res/drawable-xxhdpi/ic_lbry.png deleted file mode 100644 index 048f3bbe..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_lbry.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_library.png b/app/src/main/res/drawable-xxhdpi/ic_library.png new file mode 100644 index 00000000..f0df0523 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_library.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_lock.png b/app/src/main/res/drawable-xxhdpi/ic_lock.png new file mode 100644 index 00000000..ea7292b5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_lock.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_new.png b/app/src/main/res/drawable-xxhdpi/ic_new.png new file mode 100644 index 00000000..e10a418b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_new.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_pause.png b/app/src/main/res/drawable-xxhdpi/ic_pause.png new file mode 100644 index 00000000..3073536d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_pause.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_photo.png b/app/src/main/res/drawable-xxhdpi/ic_photo.png new file mode 100644 index 00000000..9a0ecd70 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_photo.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_play.png b/app/src/main/res/drawable-xxhdpi/ic_play.png new file mode 100644 index 00000000..0e5abef3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_play.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_publish.png b/app/src/main/res/drawable-xxhdpi/ic_publish.png new file mode 100644 index 00000000..44167620 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_publish.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_publishes.png b/app/src/main/res/drawable-xxhdpi/ic_publishes.png new file mode 100644 index 00000000..ab71c39e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_publishes.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_record.png b/app/src/main/res/drawable-xxhdpi/ic_record.png new file mode 100644 index 00000000..256cc26b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_record.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_replay_10.png b/app/src/main/res/drawable-xxhdpi/ic_replay_10.png new file mode 100644 index 00000000..7a43cc57 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_replay_10.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_report.png b/app/src/main/res/drawable-xxhdpi/ic_report.png new file mode 100644 index 00000000..4eb94dd2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_report.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_search.png b/app/src/main/res/drawable-xxhdpi/ic_search.png new file mode 100644 index 00000000..64b54caf Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_search.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_settings.png b/app/src/main/res/drawable-xxhdpi/ic_settings.png new file mode 100644 index 00000000..80e49bb7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_settings.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_share.png b/app/src/main/res/drawable-xxhdpi/ic_share.png new file mode 100644 index 00000000..edd9c312 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_share.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_stop.png b/app/src/main/res/drawable-xxhdpi/ic_stop.png new file mode 100644 index 00000000..98e9f13d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_stop.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_top.png b/app/src/main/res/drawable-xxhdpi/ic_top.png new file mode 100644 index 00000000..3e289889 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_top.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_trending.png b/app/src/main/res/drawable-xxhdpi/ic_trending.png new file mode 100644 index 00000000..b4e6af6f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_trending.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_upload.png b/app/src/main/res/drawable-xxhdpi/ic_upload.png new file mode 100644 index 00000000..44167620 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_upload.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_wallet.png b/app/src/main/res/drawable-xxhdpi/ic_wallet.png new file mode 100644 index 00000000..c0d7666a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_wallet.png differ diff --git a/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png b/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png deleted file mode 100644 index 15a983a6..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigation_src_views_assets_backicon.png b/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigation_src_views_assets_backicon.png deleted file mode 100644 index 15a983a6..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigation_src_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png b/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png deleted file mode 100644 index 15a983a6..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png b/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png deleted file mode 100644 index 15a983a6..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png b/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png deleted file mode 100644 index 15a983a6..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/stripe_2x.png b/app/src/main/res/drawable-xxhdpi/stripe_2x.png new file mode 100644 index 00000000..e377b672 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/stripe_2x.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_lbry.png b/app/src/main/res/drawable-xxxhdpi/ic_lbry.png deleted file mode 100644 index c5ff4133..00000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_lbry.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png b/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png deleted file mode 100644 index 17e52e85..00000000 Binary files a/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigation_node_modules_reactnavigationstack_lib_module_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigation_src_views_assets_backicon.png b/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigation_src_views_assets_backicon.png deleted file mode 100644 index 17e52e85..00000000 Binary files a/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigation_src_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png b/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png deleted file mode 100644 index 17e52e85..00000000 Binary files a/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigationstack_dist_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png b/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png deleted file mode 100644 index 17e52e85..00000000 Binary files a/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigationstack_lib_module_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png b/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png deleted file mode 100644 index 17e52e85..00000000 Binary files a/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigationstack_src_views_assets_backicon.png and /dev/null differ diff --git a/app/src/main/res/drawable/.gitkeep b/app/src/main/res/drawable/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/src/main/res/drawable/bg_all_icon.xml b/app/src/main/res/drawable/bg_all_icon.xml new file mode 100644 index 00000000..e6e37658 --- /dev/null +++ b/app/src/main/res/drawable/bg_all_icon.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_channel_icon.xml b/app/src/main/res/drawable/bg_channel_icon.xml new file mode 100644 index 00000000..57eb9801 --- /dev/null +++ b/app/src/main/res/drawable/bg_channel_icon.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_channel_overlay_icon.xml b/app/src/main/res/drawable/bg_channel_overlay_icon.xml new file mode 100644 index 00000000..554923d7 --- /dev/null +++ b/app/src/main/res/drawable/bg_channel_overlay_icon.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_copyable_text.xml b/app/src/main/res/drawable/bg_copyable_text.xml new file mode 100644 index 00000000..baac4c1f --- /dev/null +++ b/app/src/main/res/drawable/bg_copyable_text.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_floating_balance.xml b/app/src/main/res/drawable/bg_floating_balance.xml new file mode 100644 index 00000000..56b4ed14 --- /dev/null +++ b/app/src/main/res/drawable/bg_floating_balance.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_floating_reward.xml b/app/src/main/res/drawable/bg_floating_reward.xml new file mode 100644 index 00000000..524e0a45 --- /dev/null +++ b/app/src/main/res/drawable/bg_floating_reward.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_selected_item.xml b/app/src/main/res/drawable/bg_selected_item.xml new file mode 100644 index 00000000..7e782807 --- /dev/null +++ b/app/src/main/res/drawable/bg_selected_item.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_selected_list_item.xml b/app/src/main/res/drawable/bg_selected_list_item.xml new file mode 100644 index 00000000..9f32fbbe --- /dev/null +++ b/app/src/main/res/drawable/bg_selected_list_item.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_selected_nav_item.xml b/app/src/main/res/drawable/bg_selected_nav_item.xml new file mode 100644 index 00000000..8da95b8e --- /dev/null +++ b/app/src/main/res/drawable/bg_selected_nav_item.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_small_icon.xml b/app/src/main/res/drawable/bg_small_icon.xml new file mode 100644 index 00000000..c7ac9951 --- /dev/null +++ b/app/src/main/res/drawable/bg_small_icon.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_stream_cost.xml b/app/src/main/res/drawable/bg_stream_cost.xml new file mode 100644 index 00000000..2d0dfb26 --- /dev/null +++ b/app/src/main/res/drawable/bg_stream_cost.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_stream_overlay_icon.xml b/app/src/main/res/drawable/bg_stream_overlay_icon.xml new file mode 100644 index 00000000..4f311895 --- /dev/null +++ b/app/src/main/res/drawable/bg_stream_overlay_icon.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_tag.xml b/app/src/main/res/drawable/bg_tag.xml new file mode 100644 index 00000000..aa70d0db --- /dev/null +++ b/app/src/main/res/drawable/bg_tag.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_tag_mature.xml b/app/src/main/res/drawable/bg_tag_mature.xml new file mode 100644 index 00000000..418e1053 --- /dev/null +++ b/app/src/main/res/drawable/bg_tag_mature.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_thumbnail_icon.xml b/app/src/main/res/drawable/bg_thumbnail_icon.xml new file mode 100644 index 00000000..17877cf9 --- /dev/null +++ b/app/src/main/res/drawable/bg_thumbnail_icon.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_thumbnail_placeholder.xml b/app/src/main/res/drawable/bg_thumbnail_placeholder.xml new file mode 100644 index 00000000..41ab809d --- /dev/null +++ b/app/src/main/res/drawable/bg_thumbnail_placeholder.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/determinate_progress_circle.xml b/app/src/main/res/drawable/determinate_progress_circle.xml new file mode 100644 index 00000000..6a3b2303 --- /dev/null +++ b/app/src/main/res/drawable/determinate_progress_circle.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/fg_nav_menu_item.xml b/app/src/main/res/drawable/fg_nav_menu_item.xml new file mode 100644 index 00000000..ee7d0e64 --- /dev/null +++ b/app/src/main/res/drawable/fg_nav_menu_item.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..ca3826a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_menu_camera.xml b/app/src/main/res/drawable/ic_menu_camera.xml new file mode 100644 index 00000000..634fe922 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_camera.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_menu_gallery.xml b/app/src/main/res/drawable/ic_menu_gallery.xml new file mode 100644 index 00000000..03c77099 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_gallery.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_slideshow.xml b/app/src/main/res/drawable/ic_menu_slideshow.xml new file mode 100644 index 00000000..5e9e163a --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_slideshow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon.png b/app/src/main/res/drawable/icon.png deleted file mode 100644 index fb78a16b..00000000 Binary files a/app/src/main/res/drawable/icon.png and /dev/null differ diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml new file mode 100644 index 00000000..6d81870b --- /dev/null +++ b/app/src/main/res/drawable/side_nav_bar.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/thin_divider.xml b/app/src/main/res/drawable/thin_divider.xml new file mode 100644 index 00000000..ea24b286 --- /dev/null +++ b/app/src/main/res/drawable/thin_divider.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/font/inter.xml b/app/src/main/res/font/inter.xml new file mode 100644 index 00000000..7a51878a --- /dev/null +++ b/app/src/main/res/font/inter.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/fonts/Inter-Bold.otf b/app/src/main/res/font/inter_bold.otf similarity index 62% rename from app/src/main/assets/fonts/Inter-Bold.otf rename to app/src/main/res/font/inter_bold.otf index 0dbf67b7..43b12eab 100644 Binary files a/app/src/main/assets/fonts/Inter-Bold.otf and b/app/src/main/res/font/inter_bold.otf differ diff --git a/app/src/main/res/font/inter_bolditalic.otf b/app/src/main/res/font/inter_bolditalic.otf new file mode 100644 index 00000000..a07fdd57 Binary files /dev/null and b/app/src/main/res/font/inter_bolditalic.otf differ diff --git a/app/src/main/res/font/inter_italic.otf b/app/src/main/res/font/inter_italic.otf new file mode 100644 index 00000000..7b63db5e Binary files /dev/null and b/app/src/main/res/font/inter_italic.otf differ diff --git a/app/src/main/res/font/inter_light.otf b/app/src/main/res/font/inter_light.otf new file mode 100644 index 00000000..d4f94c37 Binary files /dev/null and b/app/src/main/res/font/inter_light.otf differ diff --git a/app/src/main/res/font/inter_lightitalic.otf b/app/src/main/res/font/inter_lightitalic.otf new file mode 100644 index 00000000..9a4b7a03 Binary files /dev/null and b/app/src/main/res/font/inter_lightitalic.otf differ diff --git a/app/src/main/assets/fonts/Inter-Regular.otf b/app/src/main/res/font/inter_regular.otf similarity index 57% rename from app/src/main/assets/fonts/Inter-Regular.otf rename to app/src/main/res/font/inter_regular.otf index 6724353f..c0dab1bf 100644 Binary files a/app/src/main/assets/fonts/Inter-Regular.otf and b/app/src/main/res/font/inter_regular.otf differ diff --git a/app/src/main/assets/fonts/Inter-SemiBold.otf b/app/src/main/res/font/inter_semibold.otf similarity index 63% rename from app/src/main/assets/fonts/Inter-SemiBold.otf rename to app/src/main/res/font/inter_semibold.otf index a6f9b2dc..4c238105 100644 Binary files a/app/src/main/assets/fonts/Inter-SemiBold.otf and b/app/src/main/res/font/inter_semibold.otf differ diff --git a/app/src/main/res/layout/activity_first_run.xml b/app/src/main/res/layout/activity_first_run.xml new file mode 100644 index 00000000..13ced125 --- /dev/null +++ b/app/src/main/res/layout/activity_first_run.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..800b76a1 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_verification.xml b/app/src/main/res/layout/activity_verification.xml new file mode 100644 index 00000000..04fddc26 --- /dev/null +++ b/app/src/main/res/layout/activity_verification.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_video.xml b/app/src/main/res/layout/activity_video.xml new file mode 100644 index 00000000..3425222b --- /dev/null +++ b/app/src/main/res/layout/activity_video.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml new file mode 100644 index 00000000..212efa4c --- /dev/null +++ b/app/src/main/res/layout/app_bar_main.xml @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_invites_by_email.xml b/app/src/main/res/layout/card_invites_by_email.xml new file mode 100644 index 00000000..e11fe386 --- /dev/null +++ b/app/src/main/res/layout/card_invites_by_email.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_invites_by_link.xml b/app/src/main/res/layout/card_invites_by_link.xml new file mode 100644 index 00000000..82853634 --- /dev/null +++ b/app/src/main/res/layout/card_invites_by_link.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_invites_history.xml b/app/src/main/res/layout/card_invites_history.xml new file mode 100644 index 00000000..7965d4e6 --- /dev/null +++ b/app/src/main/res/layout/card_invites_history.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_wallet_balance.xml b/app/src/main/res/layout/card_wallet_balance.xml new file mode 100644 index 00000000..3468bdad --- /dev/null +++ b/app/src/main/res/layout/card_wallet_balance.xml @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_wallet_receive_credits.xml b/app/src/main/res/layout/card_wallet_receive_credits.xml new file mode 100644 index 00000000..dc340eaf --- /dev/null +++ b/app/src/main/res/layout/card_wallet_receive_credits.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_wallet_recent_transactions.xml b/app/src/main/res/layout/card_wallet_recent_transactions.xml new file mode 100644 index 00000000..3a67e1d5 --- /dev/null +++ b/app/src/main/res/layout/card_wallet_recent_transactions.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_wallet_send_credits.xml b/app/src/main/res/layout/card_wallet_send_credits.xml new file mode 100644 index 00000000..ec65e681 --- /dev/null +++ b/app/src/main/res/layout/card_wallet_send_credits.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/card_wallet_sync.xml b/app/src/main/res/layout/card_wallet_sync.xml new file mode 100644 index 00000000..9ba7e51d --- /dev/null +++ b/app/src/main/res/layout/card_wallet_sync.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/container_inline_channel_form.xml b/app/src/main/res/layout/container_inline_channel_form.xml new file mode 100644 index 00000000..adad2a95 --- /dev/null +++ b/app/src/main/res/layout/container_inline_channel_form.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/container_inline_tag_form.xml b/app/src/main/res/layout/container_inline_tag_form.xml new file mode 100644 index 00000000..bdd32a3c --- /dev/null +++ b/app/src/main/res/layout/container_inline_tag_form.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/container_nothing_at_location.xml b/app/src/main/res/layout/container_nothing_at_location.xml new file mode 100644 index 00000000..fda6b591 --- /dev/null +++ b/app/src/main/res/layout/container_nothing_at_location.xml @@ -0,0 +1,41 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/container_sdk_initializing.xml b/app/src/main/res/layout/container_sdk_initializing.xml new file mode 100644 index 00000000..63f32959 --- /dev/null +++ b/app/src/main/res/layout/container_sdk_initializing.xml @@ -0,0 +1,31 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml new file mode 100644 index 00000000..2e823833 --- /dev/null +++ b/app/src/main/res/layout/content_main.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_content_from.xml b/app/src/main/res/layout/dialog_content_from.xml new file mode 100644 index 00000000..cf99a858 --- /dev/null +++ b/app/src/main/res/layout/dialog_content_from.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_content_scope.xml b/app/src/main/res/layout/dialog_content_scope.xml new file mode 100644 index 00000000..cffafa04 --- /dev/null +++ b/app/src/main/res/layout/dialog_content_scope.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_content_sort.xml b/app/src/main/res/layout/dialog_content_sort.xml new file mode 100644 index 00000000..593bfd67 --- /dev/null +++ b/app/src/main/res/layout/dialog_content_sort.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_customize_tags.xml b/app/src/main/res/layout/dialog_customize_tags.xml new file mode 100644 index 00000000..b19a9ff9 --- /dev/null +++ b/app/src/main/res/layout/dialog_customize_tags.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_discover.xml b/app/src/main/res/layout/dialog_discover.xml new file mode 100644 index 00000000..e9a32daf --- /dev/null +++ b/app/src/main/res/layout/dialog_discover.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_repost_claim.xml b/app/src/main/res/layout/dialog_repost_claim.xml new file mode 100644 index 00000000..7cbed97f --- /dev/null +++ b/app/src/main/res/layout/dialog_repost_claim.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_send_tip.xml b/app/src/main/res/layout/dialog_send_tip.xml new file mode 100644 index 00000000..6912778a --- /dev/null +++ b/app/src/main/res/layout/dialog_send_tip.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/exo_playback_control_view.xml b/app/src/main/res/layout/exo_playback_control_view.xml new file mode 100644 index 00000000..235ae4b1 --- /dev/null +++ b/app/src/main/res/layout/exo_playback_control_view.xml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/floating_wallet_balance.xml b/app/src/main/res/layout/floating_wallet_balance.xml new file mode 100644 index 00000000..2ccb21c2 --- /dev/null +++ b/app/src/main/res/layout/floating_wallet_balance.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml new file mode 100644 index 00000000..5f5e3fb9 --- /dev/null +++ b/app/src/main/res/layout/fragment_about.xml @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_all_content.xml b/app/src/main/res/layout/fragment_all_content.xml new file mode 100644 index 00000000..b1e8ef79 --- /dev/null +++ b/app/src/main/res/layout/fragment_all_content.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_channel.xml b/app/src/main/res/layout/fragment_channel.xml new file mode 100644 index 00000000..bfac6090 --- /dev/null +++ b/app/src/main/res/layout/fragment_channel.xml @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_channel_about.xml b/app/src/main/res/layout/fragment_channel_about.xml new file mode 100644 index 00000000..a572155e --- /dev/null +++ b/app/src/main/res/layout/fragment_channel_about.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_channel_content.xml b/app/src/main/res/layout/fragment_channel_content.xml new file mode 100644 index 00000000..9c30d04f --- /dev/null +++ b/app/src/main/res/layout/fragment_channel_content.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_channel_form.xml b/app/src/main/res/layout/fragment_channel_form.xml new file mode 100644 index 00000000..006a1724 --- /dev/null +++ b/app/src/main/res/layout/fragment_channel_form.xml @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_channel_manager.xml b/app/src/main/res/layout/fragment_channel_manager.xml new file mode 100644 index 00000000..f53e3429 --- /dev/null +++ b/app/src/main/res/layout/fragment_channel_manager.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_editors_choice.xml b/app/src/main/res/layout/fragment_editors_choice.xml new file mode 100644 index 00000000..42e03e9a --- /dev/null +++ b/app/src/main/res/layout/fragment_editors_choice.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_file_view.xml b/app/src/main/res/layout/fragment_file_view.xml new file mode 100644 index 00000000..9a61879a --- /dev/null +++ b/app/src/main/res/layout/fragment_file_view.xml @@ -0,0 +1,641 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_following.xml b/app/src/main/res/layout/fragment_following.xml new file mode 100644 index 00000000..48fd73fe --- /dev/null +++ b/app/src/main/res/layout/fragment_following.xml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_gallery.xml b/app/src/main/res/layout/fragment_gallery.xml new file mode 100644 index 00000000..643fe254 --- /dev/null +++ b/app/src/main/res/layout/fragment_gallery.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml new file mode 100644 index 00000000..f3d9b08f --- /dev/null +++ b/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_invites.xml b/app/src/main/res/layout/fragment_invites.xml new file mode 100644 index 00000000..67efda37 --- /dev/null +++ b/app/src/main/res/layout/fragment_invites.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml new file mode 100644 index 00000000..52df464d --- /dev/null +++ b/app/src/main/res/layout/fragment_library.xml @@ -0,0 +1,356 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_publish.xml b/app/src/main/res/layout/fragment_publish.xml new file mode 100644 index 00000000..fcc19830 --- /dev/null +++ b/app/src/main/res/layout/fragment_publish.xml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_publish_form.xml b/app/src/main/res/layout/fragment_publish_form.xml new file mode 100644 index 00000000..7d24d34f --- /dev/null +++ b/app/src/main/res/layout/fragment_publish_form.xml @@ -0,0 +1,579 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_publishes.xml b/app/src/main/res/layout/fragment_publishes.xml new file mode 100644 index 00000000..e3af5bb5 --- /dev/null +++ b/app/src/main/res/layout/fragment_publishes.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_rewards.xml b/app/src/main/res/layout/fragment_rewards.xml new file mode 100644 index 00000000..46838086 --- /dev/null +++ b/app/src/main/res/layout/fragment_rewards.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml new file mode 100644 index 00000000..b8126321 --- /dev/null +++ b/app/src/main/res/layout/fragment_search.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_slideshow.xml b/app/src/main/res/layout/fragment_slideshow.xml new file mode 100644 index 00000000..2141a333 --- /dev/null +++ b/app/src/main/res/layout/fragment_slideshow.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_transaction_history.xml b/app/src/main/res/layout/fragment_transaction_history.xml new file mode 100644 index 00000000..2762311d --- /dev/null +++ b/app/src/main/res/layout/fragment_transaction_history.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_verification_email.xml b/app/src/main/res/layout/fragment_verification_email.xml new file mode 100644 index 00000000..42663abf --- /dev/null +++ b/app/src/main/res/layout/fragment_verification_email.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_verification_manual.xml b/app/src/main/res/layout/fragment_verification_manual.xml new file mode 100644 index 00000000..5e43ceae --- /dev/null +++ b/app/src/main/res/layout/fragment_verification_manual.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_verification_phone.xml b/app/src/main/res/layout/fragment_verification_phone.xml new file mode 100644 index 00000000..6048ef33 --- /dev/null +++ b/app/src/main/res/layout/fragment_verification_phone.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_verification_wallet.xml b/app/src/main/res/layout/fragment_verification_wallet.xml new file mode 100644 index 00000000..273903e0 --- /dev/null +++ b/app/src/main/res/layout/fragment_verification_wallet.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml new file mode 100644 index 00000000..61e5e0ae --- /dev/null +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_channel.xml b/app/src/main/res/layout/list_item_channel.xml new file mode 100644 index 00000000..0469c640 --- /dev/null +++ b/app/src/main/res/layout/list_item_channel.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_channel_filter.xml b/app/src/main/res/layout/list_item_channel_filter.xml new file mode 100644 index 00000000..63970ca2 --- /dev/null +++ b/app/src/main/res/layout/list_item_channel_filter.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_editors_choice.xml b/app/src/main/res/layout/list_item_editors_choice.xml new file mode 100644 index 00000000..c616dea1 --- /dev/null +++ b/app/src/main/res/layout/list_item_editors_choice.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_featured_search_result.xml b/app/src/main/res/layout/list_item_featured_search_result.xml new file mode 100644 index 00000000..0804a85d --- /dev/null +++ b/app/src/main/res/layout/list_item_featured_search_result.xml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_gallery.xml b/app/src/main/res/layout/list_item_gallery.xml new file mode 100644 index 00000000..d18d75ed --- /dev/null +++ b/app/src/main/res/layout/list_item_gallery.xml @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_invitee.xml b/app/src/main/res/layout/list_item_invitee.xml new file mode 100644 index 00000000..57c3f597 --- /dev/null +++ b/app/src/main/res/layout/list_item_invitee.xml @@ -0,0 +1,37 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_nav_menu_group.xml b/app/src/main/res/layout/list_item_nav_menu_group.xml new file mode 100644 index 00000000..fb402dea --- /dev/null +++ b/app/src/main/res/layout/list_item_nav_menu_group.xml @@ -0,0 +1,18 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_nav_menu_item.xml b/app/src/main/res/layout/list_item_nav_menu_item.xml new file mode 100644 index 00000000..19fe7943 --- /dev/null +++ b/app/src/main/res/layout/list_item_nav_menu_item.xml @@ -0,0 +1,31 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_reward.xml b/app/src/main/res/layout/list_item_reward.xml new file mode 100644 index 00000000..fec233be --- /dev/null +++ b/app/src/main/res/layout/list_item_reward.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_stream.xml b/app/src/main/res/layout/list_item_stream.xml new file mode 100644 index 00000000..04f785a8 --- /dev/null +++ b/app/src/main/res/layout/list_item_stream.xml @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_suggested_channel.xml b/app/src/main/res/layout/list_item_suggested_channel.xml new file mode 100644 index 00000000..9c461b53 --- /dev/null +++ b/app/src/main/res/layout/list_item_suggested_channel.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_tag.xml b/app/src/main/res/layout/list_item_tag.xml new file mode 100644 index 00000000..9881babe --- /dev/null +++ b/app/src/main/res/layout/list_item_tag.xml @@ -0,0 +1,31 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_transaction.xml b/app/src/main/res/layout/list_item_transaction.xml new file mode 100644 index 00000000..14e1ccf7 --- /dev/null +++ b/app/src/main/res/layout/list_item_transaction.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_url_suggestion.xml b/app/src/main/res/layout/list_item_url_suggestion.xml new file mode 100644 index 00000000..335c865e --- /dev/null +++ b/app/src/main/res/layout/list_item_url_suggestion.xml @@ -0,0 +1,44 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layout/nav_header_main.xml new file mode 100644 index 00000000..c30ccbf5 --- /dev/null +++ b/app/src/main/res/layout/nav_header_main.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/spinner_item_channel.xml b/app/src/main/res/layout/spinner_item_channel.xml new file mode 100644 index 00000000..bcc2f763 --- /dev/null +++ b/app/src/main/res/layout/spinner_item_channel.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/spinner_item_generic.xml b/app/src/main/res/layout/spinner_item_generic.xml new file mode 100644 index 00000000..fc84a1ee --- /dev/null +++ b/app/src/main/res/layout/spinner_item_generic.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_claim_list.xml b/app/src/main/res/menu/menu_claim_list.xml new file mode 100644 index 00000000..968f7dc6 --- /dev/null +++ b/app/src/main/res/menu/menu_claim_list.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..c4a603d4 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..c4a603d4 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index a2f59082..f7533d43 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..15eb8049 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 1b523998..4319062c 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index ff10afd6..b465e06a 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..87146093 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 115a4c76..93807367 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index dcd3cd80..55844606 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..a2c14e06 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 459ca609..b2a63b40 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 8ca12fe0..6370e492 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..7e344d2b Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 8e19b410..896fc998 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index b824ebdd..c5112355 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..2d329001 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 4c19a13c..6707ab40 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/raw/node_modules_nodeemoji_lib_emoji.json b/app/src/main/res/raw/node_modules_nodeemoji_lib_emoji.json deleted file mode 100644 index 4fbb1c4a..00000000 --- a/app/src/main/res/raw/node_modules_nodeemoji_lib_emoji.json +++ /dev/null @@ -1 +0,0 @@ -{"100":"💯","1234":"🔢","umbrella_with_rain_drops":"☔","coffee":"☕","aries":"♈","taurus":"♉","sagittarius":"♐","capricorn":"♑","aquarius":"♒","pisces":"♓","anchor":"⚓","white_check_mark":"✅","sparkles":"✨","question":"❓","grey_question":"❔","grey_exclamation":"❕","exclamation":"❗","heavy_exclamation_mark":"❗","heavy_plus_sign":"➕","heavy_minus_sign":"➖","heavy_division_sign":"➗","hash":"#️⃣","keycap_star":"*️⃣","zero":"0️⃣","one":"1️⃣","two":"2️⃣","three":"3️⃣","four":"4️⃣","five":"5️⃣","six":"6️⃣","seven":"7️⃣","eight":"8️⃣","nine":"9️⃣","copyright":"©️","registered":"®️","mahjong":"🀄","black_joker":"🃏","a":"🅰️","b":"🅱️","o2":"🅾️","parking":"🅿️","ab":"🆎","cl":"🆑","cool":"🆒","free":"🆓","id":"🆔","new":"🆕","ng":"🆖","ok":"🆗","sos":"🆘","up":"🆙","vs":"🆚","flag-ac":"🇦🇨","flag-ad":"🇦🇩","flag-ae":"🇦🇪","flag-af":"🇦🇫","flag-ag":"🇦🇬","flag-ai":"🇦🇮","flag-al":"🇦🇱","flag-am":"🇦🇲","flag-ao":"🇦🇴","flag-aq":"🇦🇶","flag-ar":"🇦🇷","flag-as":"🇦🇸","flag-at":"🇦🇹","flag-au":"🇦🇺","flag-aw":"🇦🇼","flag-ax":"🇦🇽","flag-az":"🇦🇿","flag-ba":"🇧🇦","flag-bb":"🇧🇧","flag-bd":"🇧🇩","flag-be":"🇧🇪","flag-bf":"🇧🇫","flag-bg":"🇧🇬","flag-bh":"🇧🇭","flag-bi":"🇧🇮","flag-bj":"🇧🇯","flag-bl":"🇧🇱","flag-bm":"🇧🇲","flag-bn":"🇧🇳","flag-bo":"🇧🇴","flag-bq":"🇧🇶","flag-br":"🇧🇷","flag-bs":"🇧🇸","flag-bt":"🇧🇹","flag-bv":"🇧🇻","flag-bw":"🇧🇼","flag-by":"🇧🇾","flag-bz":"🇧🇿","flag-ca":"🇨🇦","flag-cc":"🇨🇨","flag-cd":"🇨🇩","flag-cf":"🇨🇫","flag-cg":"🇨🇬","flag-ch":"🇨🇭","flag-ci":"🇨🇮","flag-ck":"🇨🇰","flag-cl":"🇨🇱","flag-cm":"🇨🇲","cn":"🇨🇳","flag-cn":"🇨🇳","flag-co":"🇨🇴","flag-cp":"🇨🇵","flag-cr":"🇨🇷","flag-cu":"🇨🇺","flag-cv":"🇨🇻","flag-cw":"🇨🇼","flag-cx":"🇨🇽","flag-cy":"🇨🇾","flag-cz":"🇨🇿","de":"🇩🇪","flag-de":"🇩🇪","flag-dg":"🇩🇬","flag-dj":"🇩🇯","flag-dk":"🇩🇰","flag-dm":"🇩🇲","flag-do":"🇩🇴","flag-dz":"🇩🇿","flag-ea":"🇪🇦","flag-ec":"🇪🇨","flag-ee":"🇪🇪","flag-eg":"🇪🇬","flag-eh":"🇪🇭","flag-er":"🇪🇷","es":"🇪🇸","flag-es":"🇪🇸","flag-et":"🇪🇹","flag-eu":"🇪🇺","flag-fi":"🇫🇮","flag-fj":"🇫🇯","flag-fk":"🇫🇰","flag-fm":"🇫🇲","flag-fo":"🇫🇴","fr":"🇫🇷","flag-fr":"🇫🇷","flag-ga":"🇬🇦","gb":"🇬🇧","uk":"🇬🇧","flag-gb":"🇬🇧","flag-gd":"🇬🇩","flag-ge":"🇬🇪","flag-gf":"🇬🇫","flag-gg":"🇬🇬","flag-gh":"🇬🇭","flag-gi":"🇬🇮","flag-gl":"🇬🇱","flag-gm":"🇬🇲","flag-gn":"🇬🇳","flag-gp":"🇬🇵","flag-gq":"🇬🇶","flag-gr":"🇬🇷","flag-gs":"🇬🇸","flag-gt":"🇬🇹","flag-gu":"🇬🇺","flag-gw":"🇬🇼","flag-gy":"🇬🇾","flag-hk":"🇭🇰","flag-hm":"🇭🇲","flag-hn":"🇭🇳","flag-hr":"🇭🇷","flag-ht":"🇭🇹","flag-hu":"🇭🇺","flag-ic":"🇮🇨","flag-id":"🇮🇩","flag-ie":"🇮🇪","flag-il":"🇮🇱","flag-im":"🇮🇲","flag-in":"🇮🇳","flag-io":"🇮🇴","flag-iq":"🇮🇶","flag-ir":"🇮🇷","flag-is":"🇮🇸","it":"🇮🇹","flag-it":"🇮🇹","flag-je":"🇯🇪","flag-jm":"🇯🇲","flag-jo":"🇯🇴","jp":"🇯🇵","flag-jp":"🇯🇵","flag-ke":"🇰🇪","flag-kg":"🇰🇬","flag-kh":"🇰🇭","flag-ki":"🇰🇮","flag-km":"🇰🇲","flag-kn":"🇰🇳","flag-kp":"🇰🇵","kr":"🇰🇷","flag-kr":"🇰🇷","flag-kw":"🇰🇼","flag-ky":"🇰🇾","flag-kz":"🇰🇿","flag-la":"🇱🇦","flag-lb":"🇱🇧","flag-lc":"🇱🇨","flag-li":"🇱🇮","flag-lk":"🇱🇰","flag-lr":"🇱🇷","flag-ls":"🇱🇸","flag-lt":"🇱🇹","flag-lu":"🇱🇺","flag-lv":"🇱🇻","flag-ly":"🇱🇾","flag-ma":"🇲🇦","flag-mc":"🇲🇨","flag-md":"🇲🇩","flag-me":"🇲🇪","flag-mf":"🇲🇫","flag-mg":"🇲🇬","flag-mh":"🇲🇭","flag-mk":"🇲🇰","flag-ml":"🇲🇱","flag-mm":"🇲🇲","flag-mn":"🇲🇳","flag-mo":"🇲🇴","flag-mp":"🇲🇵","flag-mq":"🇲🇶","flag-mr":"🇲🇷","flag-ms":"🇲🇸","flag-mt":"🇲🇹","flag-mu":"🇲🇺","flag-mv":"🇲🇻","flag-mw":"🇲🇼","flag-mx":"🇲🇽","flag-my":"🇲🇾","flag-mz":"🇲🇿","flag-na":"🇳🇦","flag-nc":"🇳🇨","flag-ne":"🇳🇪","flag-nf":"🇳🇫","flag-ng":"🇳🇬","flag-ni":"🇳🇮","flag-nl":"🇳🇱","flag-no":"🇳🇴","flag-np":"🇳🇵","flag-nr":"🇳🇷","flag-nu":"🇳🇺","flag-nz":"🇳🇿","flag-om":"🇴🇲","flag-pa":"🇵🇦","flag-pe":"🇵🇪","flag-pf":"🇵🇫","flag-pg":"🇵🇬","flag-ph":"🇵🇭","flag-pk":"🇵🇰","flag-pl":"🇵🇱","flag-pm":"🇵🇲","flag-pn":"🇵🇳","flag-pr":"🇵🇷","flag-ps":"🇵🇸","flag-pt":"🇵🇹","flag-pw":"🇵🇼","flag-py":"🇵🇾","flag-qa":"🇶🇦","flag-re":"🇷🇪","flag-ro":"🇷🇴","flag-rs":"🇷🇸","ru":"🇷🇺","flag-ru":"🇷🇺","flag-rw":"🇷🇼","flag-sa":"🇸🇦","flag-sb":"🇸🇧","flag-sc":"🇸🇨","flag-sd":"🇸🇩","flag-se":"🇸🇪","flag-sg":"🇸🇬","flag-sh":"🇸🇭","flag-si":"🇸🇮","flag-sj":"🇸🇯","flag-sk":"🇸🇰","flag-sl":"🇸🇱","flag-sm":"🇸🇲","flag-sn":"🇸🇳","flag-so":"🇸🇴","flag-sr":"🇸🇷","flag-ss":"🇸🇸","flag-st":"🇸🇹","flag-sv":"🇸🇻","flag-sx":"🇸🇽","flag-sy":"🇸🇾","flag-sz":"🇸🇿","flag-ta":"🇹🇦","flag-tc":"🇹🇨","flag-td":"🇹🇩","flag-tf":"🇹🇫","flag-tg":"🇹🇬","flag-th":"🇹🇭","flag-tj":"🇹🇯","flag-tk":"🇹🇰","flag-tl":"🇹🇱","flag-tm":"🇹🇲","flag-tn":"🇹🇳","flag-to":"🇹🇴","flag-tr":"🇹🇷","flag-tt":"🇹🇹","flag-tv":"🇹🇻","flag-tw":"🇹🇼","flag-tz":"🇹🇿","flag-ua":"🇺🇦","flag-ug":"🇺🇬","flag-um":"🇺🇲","flag-un":"🇺🇳","us":"🇺🇸","flag-us":"🇺🇸","flag-uy":"🇺🇾","flag-uz":"🇺🇿","flag-va":"🇻🇦","flag-vc":"🇻🇨","flag-ve":"🇻🇪","flag-vg":"🇻🇬","flag-vi":"🇻🇮","flag-vn":"🇻🇳","flag-vu":"🇻🇺","flag-wf":"🇼🇫","flag-ws":"🇼🇸","flag-xk":"🇽🇰","flag-ye":"🇾🇪","flag-yt":"🇾🇹","flag-za":"🇿🇦","flag-zm":"🇿🇲","flag-zw":"🇿🇼","koko":"🈁","sa":"🈂️","u7121":"🈚","u6307":"🈯","u7981":"🈲","u7a7a":"🈳","u5408":"🈴","u6e80":"🈵","u6709":"🈶","u6708":"🈷️","u7533":"🈸","u5272":"🈹","u55b6":"🈺","ideograph_advantage":"🉐","accept":"🉑","cyclone":"🌀","foggy":"🌁","closed_umbrella":"🌂","night_with_stars":"🌃","sunrise_over_mountains":"🌄","sunrise":"🌅","city_sunset":"🌆","city_sunrise":"🌇","rainbow":"🌈","bridge_at_night":"🌉","ocean":"🌊","volcano":"🌋","milky_way":"🌌","earth_africa":"🌍","earth_americas":"🌎","earth_asia":"🌏","globe_with_meridians":"🌐","new_moon":"🌑","waxing_crescent_moon":"🌒","first_quarter_moon":"🌓","moon":"🌔","waxing_gibbous_moon":"🌔","full_moon":"🌕","waning_gibbous_moon":"🌖","last_quarter_moon":"🌗","waning_crescent_moon":"🌘","crescent_moon":"🌙","new_moon_with_face":"🌚","first_quarter_moon_with_face":"🌛","last_quarter_moon_with_face":"🌜","full_moon_with_face":"🌝","sun_with_face":"🌞","star2":"🌟","stars":"🌠","thermometer":"🌡️","mostly_sunny":"🌤️","sun_small_cloud":"🌤️","barely_sunny":"🌥️","sun_behind_cloud":"🌥️","partly_sunny_rain":"🌦️","sun_behind_rain_cloud":"🌦️","rain_cloud":"🌧️","snow_cloud":"🌨️","lightning":"🌩️","lightning_cloud":"🌩️","tornado":"🌪️","tornado_cloud":"🌪️","fog":"🌫️","wind_blowing_face":"🌬️","hotdog":"🌭","taco":"🌮","burrito":"🌯","chestnut":"🌰","seedling":"🌱","evergreen_tree":"🌲","deciduous_tree":"🌳","palm_tree":"🌴","cactus":"🌵","hot_pepper":"🌶️","tulip":"🌷","cherry_blossom":"🌸","rose":"🌹","hibiscus":"🌺","sunflower":"🌻","blossom":"🌼","corn":"🌽","ear_of_rice":"🌾","herb":"🌿","four_leaf_clover":"🍀","maple_leaf":"🍁","fallen_leaf":"🍂","leaves":"🍃","mushroom":"🍄","tomato":"🍅","eggplant":"🍆","grapes":"🍇","melon":"🍈","watermelon":"🍉","tangerine":"🍊","lemon":"🍋","banana":"🍌","pineapple":"🍍","apple":"🍎","green_apple":"🍏","pear":"🍐","peach":"🍑","cherries":"🍒","strawberry":"🍓","hamburger":"🍔","pizza":"🍕","meat_on_bone":"🍖","poultry_leg":"🍗","rice_cracker":"🍘","rice_ball":"🍙","rice":"🍚","curry":"🍛","ramen":"🍜","spaghetti":"🍝","bread":"🍞","fries":"🍟","sweet_potato":"🍠","dango":"🍡","oden":"🍢","sushi":"🍣","fried_shrimp":"🍤","fish_cake":"🍥","icecream":"🍦","shaved_ice":"🍧","ice_cream":"🍨","doughnut":"🍩","cookie":"🍪","chocolate_bar":"🍫","candy":"🍬","lollipop":"🍭","custard":"🍮","honey_pot":"🍯","cake":"🍰","bento":"🍱","stew":"🍲","fried_egg":"🍳","cooking":"🍳","fork_and_knife":"🍴","tea":"🍵","sake":"🍶","wine_glass":"🍷","cocktail":"🍸","tropical_drink":"🍹","beer":"🍺","beers":"🍻","baby_bottle":"🍼","knife_fork_plate":"🍽️","champagne":"🍾","popcorn":"🍿","ribbon":"🎀","gift":"🎁","birthday":"🎂","jack_o_lantern":"🎃","christmas_tree":"🎄","santa":"🎅","fireworks":"🎆","sparkler":"🎇","balloon":"🎈","tada":"🎉","confetti_ball":"🎊","tanabata_tree":"🎋","crossed_flags":"🎌","bamboo":"🎍","dolls":"🎎","flags":"🎏","wind_chime":"🎐","rice_scene":"🎑","school_satchel":"🎒","mortar_board":"🎓","medal":"🎖️","reminder_ribbon":"🎗️","studio_microphone":"🎙️","level_slider":"🎚️","control_knobs":"🎛️","film_frames":"🎞️","admission_tickets":"🎟️","carousel_horse":"🎠","ferris_wheel":"🎡","roller_coaster":"🎢","fishing_pole_and_fish":"🎣","microphone":"🎤","movie_camera":"🎥","cinema":"🎦","headphones":"🎧","art":"🎨","tophat":"🎩","circus_tent":"🎪","ticket":"🎫","clapper":"🎬","performing_arts":"🎭","video_game":"🎮","dart":"🎯","slot_machine":"🎰","8ball":"🎱","game_die":"🎲","bowling":"🎳","flower_playing_cards":"🎴","musical_note":"🎵","notes":"🎶","saxophone":"🎷","guitar":"🎸","musical_keyboard":"🎹","trumpet":"🎺","violin":"🎻","musical_score":"🎼","running_shirt_with_sash":"🎽","tennis":"🎾","ski":"🎿","basketball":"🏀","checkered_flag":"🏁","snowboarder":"🏂","woman-running":"🏃‍♀️","man-running":"🏃‍♂️","runner":"🏃‍♂️","running":"🏃‍♂️","woman-surfing":"🏄‍♀️","man-surfing":"🏄‍♂️","surfer":"🏄‍♂️","sports_medal":"🏅","trophy":"🏆","horse_racing":"🏇","football":"🏈","rugby_football":"🏉","woman-swimming":"🏊‍♀️","man-swimming":"🏊‍♂️","swimmer":"🏊‍♂️","woman-lifting-weights":"🏋️‍♀️","man-lifting-weights":"🏋️‍♂️","weight_lifter":"🏋️‍♂️","woman-golfing":"🏌️‍♀️","man-golfing":"🏌️‍♂️","golfer":"🏌️‍♂️","racing_motorcycle":"🏍️","racing_car":"🏎️","cricket_bat_and_ball":"🏏","volleyball":"🏐","field_hockey_stick_and_ball":"🏑","ice_hockey_stick_and_puck":"🏒","table_tennis_paddle_and_ball":"🏓","snow_capped_mountain":"🏔️","camping":"🏕️","beach_with_umbrella":"🏖️","building_construction":"🏗️","house_buildings":"🏘️","cityscape":"🏙️","derelict_house_building":"🏚️","classical_building":"🏛️","desert":"🏜️","desert_island":"🏝️","national_park":"🏞️","stadium":"🏟️","house":"🏠","house_with_garden":"🏡","office":"🏢","post_office":"🏣","european_post_office":"🏤","hospital":"🏥","bank":"🏦","atm":"🏧","hotel":"🏨","love_hotel":"🏩","convenience_store":"🏪","school":"🏫","department_store":"🏬","factory":"🏭","izakaya_lantern":"🏮","lantern":"🏮","japanese_castle":"🏯","european_castle":"🏰","rainbow-flag":"🏳️‍🌈","waving_white_flag":"🏳️","flag-england":"🏴󠁧󠁢󠁥󠁮󠁧󠁿","flag-scotland":"🏴󠁧󠁢󠁳󠁣󠁴󠁿","flag-wales":"🏴󠁧󠁢󠁷󠁬󠁳󠁿","waving_black_flag":"🏴","rosette":"🏵️","label":"🏷️","badminton_racquet_and_shuttlecock":"🏸","bow_and_arrow":"🏹","amphora":"🏺","skin-tone-2":"🏻","skin-tone-3":"🏼","skin-tone-4":"🏽","skin-tone-5":"🏾","skin-tone-6":"🏿","rat":"🐀","mouse2":"🐁","ox":"🐂","water_buffalo":"🐃","cow2":"🐄","tiger2":"🐅","leopard":"🐆","rabbit2":"🐇","cat2":"🐈","dragon":"🐉","crocodile":"🐊","whale2":"🐋","snail":"🐌","snake":"🐍","racehorse":"🐎","ram":"🐏","goat":"🐐","sheep":"🐑","monkey":"🐒","rooster":"🐓","chicken":"🐔","dog2":"🐕","pig2":"🐖","boar":"🐗","elephant":"🐘","octopus":"🐙","shell":"🐚","bug":"🐛","ant":"🐜","bee":"🐝","honeybee":"🐝","beetle":"🐞","fish":"🐟","tropical_fish":"🐠","blowfish":"🐡","turtle":"🐢","hatching_chick":"🐣","baby_chick":"🐤","hatched_chick":"🐥","bird":"🐦","penguin":"🐧","koala":"🐨","poodle":"🐩","dromedary_camel":"🐪","camel":"🐫","dolphin":"🐬","flipper":"🐬","mouse":"🐭","cow":"🐮","tiger":"🐯","rabbit":"🐰","cat":"🐱","dragon_face":"🐲","whale":"🐳","horse":"🐴","monkey_face":"🐵","dog":"🐶","pig":"🐷","frog":"🐸","hamster":"🐹","wolf":"🐺","bear":"🐻","panda_face":"🐼","pig_nose":"🐽","feet":"🐾","paw_prints":"🐾","chipmunk":"🐿️","eyes":"👀","eye-in-speech-bubble":"👁️‍🗨️","eye":"👁️","ear":"👂","nose":"👃","lips":"👄","tongue":"👅","point_up_2":"👆","point_down":"👇","point_left":"👈","point_right":"👉","facepunch":"👊","punch":"👊","wave":"👋","ok_hand":"👌","+1":"👍","thumbsup":"👍","-1":"👎","thumbsdown":"👎","clap":"👏","open_hands":"👐","crown":"👑","womans_hat":"👒","eyeglasses":"👓","necktie":"👔","shirt":"👕","tshirt":"👕","jeans":"👖","dress":"👗","kimono":"👘","bikini":"👙","womans_clothes":"👚","purse":"👛","handbag":"👜","pouch":"👝","mans_shoe":"👞","shoe":"👞","athletic_shoe":"👟","high_heel":"👠","sandal":"👡","boot":"👢","footprints":"👣","bust_in_silhouette":"👤","busts_in_silhouette":"👥","boy":"👦","girl":"👧","male-farmer":"👨‍🌾","male-cook":"👨‍🍳","male-student":"👨‍🎓","male-singer":"👨‍🎤","male-artist":"👨‍🎨","male-teacher":"👨‍🏫","male-factory-worker":"👨‍🏭","man-boy-boy":"👨‍👦‍👦","man-boy":"👨‍👦","man-girl-boy":"👨‍👧‍👦","man-girl-girl":"👨‍👧‍👧","man-girl":"👨‍👧","man-man-boy":"👨‍👨‍👦","man-man-boy-boy":"👨‍👨‍👦‍👦","man-man-girl":"👨‍👨‍👧","man-man-girl-boy":"👨‍👨‍👧‍👦","man-man-girl-girl":"👨‍👨‍👧‍👧","man-woman-boy":"👨‍👩‍👦","family":"👨‍👩‍👦","man-woman-boy-boy":"👨‍👩‍👦‍👦","man-woman-girl":"👨‍👩‍👧","man-woman-girl-boy":"👨‍👩‍👧‍👦","man-woman-girl-girl":"👨‍👩‍👧‍👧","male-technologist":"👨‍💻","male-office-worker":"👨‍💼","male-mechanic":"👨‍🔧","male-scientist":"👨‍🔬","male-astronaut":"👨‍🚀","male-firefighter":"👨‍🚒","male-doctor":"👨‍⚕️","male-judge":"👨‍⚖️","male-pilot":"👨‍✈️","man-heart-man":"👨‍❤️‍👨","man-kiss-man":"👨‍❤️‍💋‍👨","man":"👨","female-farmer":"👩‍🌾","female-cook":"👩‍🍳","female-student":"👩‍🎓","female-singer":"👩‍🎤","female-artist":"👩‍🎨","female-teacher":"👩‍🏫","female-factory-worker":"👩‍🏭","woman-boy-boy":"👩‍👦‍👦","woman-boy":"👩‍👦","woman-girl-boy":"👩‍👧‍👦","woman-girl-girl":"👩‍👧‍👧","woman-girl":"👩‍👧","woman-woman-boy":"👩‍👩‍👦","woman-woman-boy-boy":"👩‍👩‍👦‍👦","woman-woman-girl":"👩‍👩‍👧","woman-woman-girl-boy":"👩‍👩‍👧‍👦","woman-woman-girl-girl":"👩‍👩‍👧‍👧","female-technologist":"👩‍💻","female-office-worker":"👩‍💼","female-mechanic":"👩‍🔧","female-scientist":"👩‍🔬","female-astronaut":"👩‍🚀","female-firefighter":"👩‍🚒","female-doctor":"👩‍⚕️","female-judge":"👩‍⚖️","female-pilot":"👩‍✈️","woman-heart-man":"👩‍❤️‍👨","couple_with_heart":"👩‍❤️‍👨","woman-heart-woman":"👩‍❤️‍👩","woman-kiss-man":"👩‍❤️‍💋‍👨","couplekiss":"👩‍❤️‍💋‍👨","woman-kiss-woman":"👩‍❤️‍💋‍👩","woman":"👩","couple":"👫","man_and_woman_holding_hands":"👫","two_men_holding_hands":"👬","two_women_holding_hands":"👭","female-police-officer":"👮‍♀️","male-police-officer":"👮‍♂️","cop":"👮‍♂️","woman-with-bunny-ears-partying":"👯‍♀️","dancers":"👯‍♀️","man-with-bunny-ears-partying":"👯‍♂️","bride_with_veil":"👰","blond-haired-woman":"👱‍♀️","blond-haired-man":"👱‍♂️","person_with_blond_hair":"👱‍♂️","man_with_gua_pi_mao":"👲","woman-wearing-turban":"👳‍♀️","man-wearing-turban":"👳‍♂️","man_with_turban":"👳‍♂️","older_man":"👴","older_woman":"👵","baby":"👶","female-construction-worker":"👷‍♀️","male-construction-worker":"👷‍♂️","construction_worker":"👷‍♂️","princess":"👸","japanese_ogre":"👹","japanese_goblin":"👺","ghost":"👻","angel":"👼","alien":"👽","space_invader":"👾","imp":"👿","skull":"💀","woman-tipping-hand":"💁‍♀️","information_desk_person":"💁‍♀️","man-tipping-hand":"💁‍♂️","female-guard":"💂‍♀️","male-guard":"💂‍♂️","guardsman":"💂‍♂️","dancer":"💃","lipstick":"💄","nail_care":"💅","woman-getting-massage":"💆‍♀️","massage":"💆‍♀️","man-getting-massage":"💆‍♂️","woman-getting-haircut":"💇‍♀️","haircut":"💇‍♀️","man-getting-haircut":"💇‍♂️","barber":"💈","syringe":"💉","pill":"💊","kiss":"💋","love_letter":"💌","ring":"💍","gem":"💎","bouquet":"💐","wedding":"💒","heartbeat":"💓","broken_heart":"💔","two_hearts":"💕","sparkling_heart":"💖","heartpulse":"💗","cupid":"💘","blue_heart":"💙","green_heart":"💚","yellow_heart":"💛","purple_heart":"💜","gift_heart":"💝","revolving_hearts":"💞","heart_decoration":"💟","diamond_shape_with_a_dot_inside":"💠","bulb":"💡","anger":"💢","bomb":"💣","zzz":"💤","boom":"💥","collision":"💥","sweat_drops":"💦","droplet":"💧","dash":"💨","hankey":"💩","poop":"💩","shit":"💩","muscle":"💪","dizzy":"💫","speech_balloon":"💬","thought_balloon":"💭","white_flower":"💮","moneybag":"💰","currency_exchange":"💱","heavy_dollar_sign":"💲","credit_card":"💳","yen":"💴","dollar":"💵","euro":"💶","pound":"💷","money_with_wings":"💸","chart":"💹","seat":"💺","computer":"💻","briefcase":"💼","minidisc":"💽","floppy_disk":"💾","cd":"💿","dvd":"📀","file_folder":"📁","open_file_folder":"📂","page_with_curl":"📃","page_facing_up":"📄","date":"📅","calendar":"📆","card_index":"📇","chart_with_upwards_trend":"📈","chart_with_downwards_trend":"📉","bar_chart":"📊","clipboard":"📋","pushpin":"📌","round_pushpin":"📍","paperclip":"📎","straight_ruler":"📏","triangular_ruler":"📐","bookmark_tabs":"📑","ledger":"📒","notebook":"📓","notebook_with_decorative_cover":"📔","closed_book":"📕","book":"📖","open_book":"📖","green_book":"📗","blue_book":"📘","orange_book":"📙","books":"📚","name_badge":"📛","scroll":"📜","memo":"📝","pencil":"📝","telephone_receiver":"📞","pager":"📟","fax":"📠","satellite_antenna":"📡","loudspeaker":"📢","mega":"📣","outbox_tray":"📤","inbox_tray":"📥","package":"📦","e-mail":"📧","incoming_envelope":"📨","envelope_with_arrow":"📩","mailbox_closed":"📪","mailbox":"📫","mailbox_with_mail":"📬","mailbox_with_no_mail":"📭","postbox":"📮","postal_horn":"📯","newspaper":"📰","iphone":"📱","calling":"📲","vibration_mode":"📳","mobile_phone_off":"📴","no_mobile_phones":"📵","signal_strength":"📶","camera":"📷","camera_with_flash":"📸","video_camera":"📹","tv":"📺","radio":"📻","vhs":"📼","film_projector":"📽️","prayer_beads":"📿","twisted_rightwards_arrows":"🔀","repeat":"🔁","repeat_one":"🔂","arrows_clockwise":"🔃","arrows_counterclockwise":"🔄","low_brightness":"🔅","high_brightness":"🔆","mute":"🔇","speaker":"🔈","sound":"🔉","loud_sound":"🔊","battery":"🔋","electric_plug":"🔌","mag":"🔍","mag_right":"🔎","lock_with_ink_pen":"🔏","closed_lock_with_key":"🔐","key":"🔑","lock":"🔒","unlock":"🔓","bell":"🔔","no_bell":"🔕","bookmark":"🔖","link":"🔗","radio_button":"🔘","back":"🔙","end":"🔚","on":"🔛","soon":"🔜","top":"🔝","underage":"🔞","keycap_ten":"🔟","capital_abcd":"🔠","abcd":"🔡","symbols":"🔣","abc":"🔤","fire":"🔥","flashlight":"🔦","wrench":"🔧","hammer":"🔨","nut_and_bolt":"🔩","hocho":"🔪","knife":"🔪","gun":"🔫","microscope":"🔬","telescope":"🔭","crystal_ball":"🔮","six_pointed_star":"🔯","beginner":"🔰","trident":"🔱","black_square_button":"🔲","white_square_button":"🔳","red_circle":"🔴","large_blue_circle":"🔵","large_orange_diamond":"🔶","large_blue_diamond":"🔷","small_orange_diamond":"🔸","small_blue_diamond":"🔹","small_red_triangle":"🔺","small_red_triangle_down":"🔻","arrow_up_small":"🔼","arrow_down_small":"🔽","om_symbol":"🕉️","dove_of_peace":"🕊️","kaaba":"🕋","mosque":"🕌","synagogue":"🕍","menorah_with_nine_branches":"🕎","clock1":"🕐","clock2":"🕑","clock3":"🕒","clock4":"🕓","clock5":"🕔","clock6":"🕕","clock7":"🕖","clock8":"🕗","clock9":"🕘","clock10":"🕙","clock11":"🕚","clock12":"🕛","clock130":"🕜","clock230":"🕝","clock330":"🕞","clock430":"🕟","clock530":"🕠","clock630":"🕡","clock730":"🕢","clock830":"🕣","clock930":"🕤","clock1030":"🕥","clock1130":"🕦","clock1230":"🕧","candle":"🕯️","mantelpiece_clock":"🕰️","hole":"🕳️","man_in_business_suit_levitating":"🕴️","female-detective":"🕵️‍♀️","male-detective":"🕵️‍♂️","sleuth_or_spy":"🕵️‍♂️","dark_sunglasses":"🕶️","spider":"🕷️","spider_web":"🕸️","joystick":"🕹️","man_dancing":"🕺","linked_paperclips":"🖇️","lower_left_ballpoint_pen":"🖊️","lower_left_fountain_pen":"🖋️","lower_left_paintbrush":"🖌️","lower_left_crayon":"🖍️","raised_hand_with_fingers_splayed":"🖐️","middle_finger":"🖕","reversed_hand_with_middle_finger_extended":"🖕","spock-hand":"🖖","black_heart":"🖤","desktop_computer":"🖥️","printer":"🖨️","three_button_mouse":"🖱️","trackball":"🖲️","frame_with_picture":"🖼️","card_index_dividers":"🗂️","card_file_box":"🗃️","file_cabinet":"🗄️","wastebasket":"🗑️","spiral_note_pad":"🗒️","spiral_calendar_pad":"🗓️","compression":"🗜️","old_key":"🗝️","rolled_up_newspaper":"🗞️","dagger_knife":"🗡️","speaking_head_in_silhouette":"🗣️","left_speech_bubble":"🗨️","right_anger_bubble":"🗯️","ballot_box_with_ballot":"🗳️","world_map":"🗺️","mount_fuji":"🗻","tokyo_tower":"🗼","statue_of_liberty":"🗽","japan":"🗾","moyai":"🗿","grinning":"😀","grin":"😁","joy":"😂","smiley":"😃","smile":"😄","sweat_smile":"😅","laughing":"😆","satisfied":"😆","innocent":"😇","smiling_imp":"😈","wink":"😉","blush":"😊","yum":"😋","relieved":"😌","heart_eyes":"😍","sunglasses":"😎","smirk":"😏","neutral_face":"😐","expressionless":"😑","unamused":"😒","sweat":"😓","pensive":"😔","confused":"😕","confounded":"😖","kissing":"😗","kissing_heart":"😘","kissing_smiling_eyes":"😙","kissing_closed_eyes":"😚","stuck_out_tongue":"😛","stuck_out_tongue_winking_eye":"😜","stuck_out_tongue_closed_eyes":"😝","disappointed":"😞","worried":"😟","angry":"😠","rage":"😡","cry":"😢","persevere":"😣","triumph":"😤","disappointed_relieved":"😥","frowning":"😦","anguished":"😧","fearful":"😨","weary":"😩","sleepy":"😪","tired_face":"😫","grimacing":"😬","sob":"😭","open_mouth":"😮","hushed":"😯","cold_sweat":"😰","scream":"😱","astonished":"😲","flushed":"😳","sleeping":"😴","dizzy_face":"😵","no_mouth":"😶","mask":"😷","smile_cat":"😸","joy_cat":"😹","smiley_cat":"😺","heart_eyes_cat":"😻","smirk_cat":"😼","kissing_cat":"😽","pouting_cat":"😾","crying_cat_face":"😿","scream_cat":"🙀","slightly_frowning_face":"🙁","slightly_smiling_face":"🙂","upside_down_face":"🙃","face_with_rolling_eyes":"🙄","woman-gesturing-no":"🙅‍♀️","no_good":"🙅‍♀️","man-gesturing-no":"🙅‍♂️","woman-gesturing-ok":"🙆‍♀️","ok_woman":"🙆‍♀️","man-gesturing-ok":"🙆‍♂️","woman-bowing":"🙇‍♀️","man-bowing":"🙇‍♂️","bow":"🙇‍♂️","see_no_evil":"🙈","hear_no_evil":"🙉","speak_no_evil":"🙊","woman-raising-hand":"🙋‍♀️","raising_hand":"🙋‍♀️","man-raising-hand":"🙋‍♂️","raised_hands":"🙌","woman-frowning":"🙍‍♀️","person_frowning":"🙍‍♀️","man-frowning":"🙍‍♂️","woman-pouting":"🙎‍♀️","person_with_pouting_face":"🙎‍♀️","man-pouting":"🙎‍♂️","pray":"🙏","rocket":"🚀","helicopter":"🚁","steam_locomotive":"🚂","railway_car":"🚃","bullettrain_side":"🚄","bullettrain_front":"🚅","train2":"🚆","metro":"🚇","light_rail":"🚈","station":"🚉","tram":"🚊","train":"🚋","bus":"🚌","oncoming_bus":"🚍","trolleybus":"🚎","busstop":"🚏","minibus":"🚐","ambulance":"🚑","fire_engine":"🚒","police_car":"🚓","oncoming_police_car":"🚔","taxi":"🚕","oncoming_taxi":"🚖","car":"🚗","red_car":"🚗","oncoming_automobile":"🚘","blue_car":"🚙","truck":"🚚","articulated_lorry":"🚛","tractor":"🚜","monorail":"🚝","mountain_railway":"🚞","suspension_railway":"🚟","mountain_cableway":"🚠","aerial_tramway":"🚡","ship":"🚢","woman-rowing-boat":"🚣‍♀️","man-rowing-boat":"🚣‍♂️","rowboat":"🚣‍♂️","speedboat":"🚤","traffic_light":"🚥","vertical_traffic_light":"🚦","construction":"🚧","rotating_light":"🚨","triangular_flag_on_post":"🚩","door":"🚪","no_entry_sign":"🚫","smoking":"🚬","no_smoking":"🚭","put_litter_in_its_place":"🚮","do_not_litter":"🚯","potable_water":"🚰","non-potable_water":"🚱","bike":"🚲","no_bicycles":"🚳","woman-biking":"🚴‍♀️","man-biking":"🚴‍♂️","bicyclist":"🚴‍♂️","woman-mountain-biking":"🚵‍♀️","man-mountain-biking":"🚵‍♂️","mountain_bicyclist":"🚵‍♂️","woman-walking":"🚶‍♀️","man-walking":"🚶‍♂️","walking":"🚶‍♂️","no_pedestrians":"🚷","children_crossing":"🚸","mens":"🚹","womens":"🚺","restroom":"🚻","baby_symbol":"🚼","toilet":"🚽","wc":"🚾","shower":"🚿","bath":"🛀","bathtub":"🛁","passport_control":"🛂","customs":"🛃","baggage_claim":"🛄","left_luggage":"🛅","couch_and_lamp":"🛋️","sleeping_accommodation":"🛌","shopping_bags":"🛍️","bellhop_bell":"🛎️","bed":"🛏️","place_of_worship":"🛐","octagonal_sign":"🛑","shopping_trolley":"🛒","hammer_and_wrench":"🛠️","shield":"🛡️","oil_drum":"🛢️","motorway":"🛣️","railway_track":"🛤️","motor_boat":"🛥️","small_airplane":"🛩️","airplane_departure":"🛫","airplane_arriving":"🛬","satellite":"🛰️","passenger_ship":"🛳️","scooter":"🛴","motor_scooter":"🛵","canoe":"🛶","sled":"🛷","flying_saucer":"🛸","zipper_mouth_face":"🤐","money_mouth_face":"🤑","face_with_thermometer":"🤒","nerd_face":"🤓","thinking_face":"🤔","face_with_head_bandage":"🤕","robot_face":"🤖","hugging_face":"🤗","the_horns":"🤘","sign_of_the_horns":"🤘","call_me_hand":"🤙","raised_back_of_hand":"🤚","left-facing_fist":"🤛","right-facing_fist":"🤜","handshake":"🤝","crossed_fingers":"🤞","hand_with_index_and_middle_fingers_crossed":"🤞","i_love_you_hand_sign":"🤟","face_with_cowboy_hat":"🤠","clown_face":"🤡","nauseated_face":"🤢","rolling_on_the_floor_laughing":"🤣","drooling_face":"🤤","lying_face":"🤥","woman-facepalming":"🤦‍♀️","man-facepalming":"🤦‍♂️","face_palm":"🤦","sneezing_face":"🤧","face_with_raised_eyebrow":"🤨","face_with_one_eyebrow_raised":"🤨","star-struck":"🤩","grinning_face_with_star_eyes":"🤩","zany_face":"🤪","grinning_face_with_one_large_and_one_small_eye":"🤪","shushing_face":"🤫","face_with_finger_covering_closed_lips":"🤫","face_with_symbols_on_mouth":"🤬","serious_face_with_symbols_covering_mouth":"🤬","face_with_hand_over_mouth":"🤭","smiling_face_with_smiling_eyes_and_hand_covering_mouth":"🤭","face_vomiting":"🤮","face_with_open_mouth_vomiting":"🤮","exploding_head":"🤯","shocked_face_with_exploding_head":"🤯","pregnant_woman":"🤰","breast-feeding":"🤱","palms_up_together":"🤲","selfie":"🤳","prince":"🤴","man_in_tuxedo":"🤵","mrs_claus":"🤶","mother_christmas":"🤶","woman-shrugging":"🤷‍♀️","man-shrugging":"🤷‍♂️","shrug":"🤷","woman-cartwheeling":"🤸‍♀️","man-cartwheeling":"🤸‍♂️","person_doing_cartwheel":"🤸","woman-juggling":"🤹‍♀️","man-juggling":"🤹‍♂️","juggling":"🤹","fencer":"🤺","woman-wrestling":"🤼‍♀️","man-wrestling":"🤼‍♂️","wrestlers":"🤼","woman-playing-water-polo":"🤽‍♀️","man-playing-water-polo":"🤽‍♂️","water_polo":"🤽","woman-playing-handball":"🤾‍♀️","man-playing-handball":"🤾‍♂️","handball":"🤾","wilted_flower":"🥀","drum_with_drumsticks":"🥁","clinking_glasses":"🥂","tumbler_glass":"🥃","spoon":"🥄","goal_net":"🥅","first_place_medal":"🥇","second_place_medal":"🥈","third_place_medal":"🥉","boxing_glove":"🥊","martial_arts_uniform":"🥋","curling_stone":"🥌","croissant":"🥐","avocado":"🥑","cucumber":"🥒","bacon":"🥓","potato":"🥔","carrot":"🥕","baguette_bread":"🥖","green_salad":"🥗","shallow_pan_of_food":"🥘","stuffed_flatbread":"🥙","egg":"🥚","glass_of_milk":"🥛","peanuts":"🥜","kiwifruit":"🥝","pancakes":"🥞","dumpling":"🥟","fortune_cookie":"🥠","takeout_box":"🥡","chopsticks":"🥢","bowl_with_spoon":"🥣","cup_with_straw":"🥤","coconut":"🥥","broccoli":"🥦","pie":"🥧","pretzel":"🥨","cut_of_meat":"🥩","sandwich":"🥪","canned_food":"🥫","crab":"🦀","lion_face":"🦁","scorpion":"🦂","turkey":"🦃","unicorn_face":"🦄","eagle":"🦅","duck":"🦆","bat":"🦇","shark":"🦈","owl":"🦉","fox_face":"🦊","butterfly":"🦋","deer":"🦌","gorilla":"🦍","lizard":"🦎","rhinoceros":"🦏","shrimp":"🦐","squid":"🦑","giraffe_face":"🦒","zebra_face":"🦓","hedgehog":"🦔","sauropod":"🦕","t-rex":"🦖","cricket":"🦗","cheese_wedge":"🧀","face_with_monocle":"🧐","adult":"🧑","child":"🧒","older_adult":"🧓","bearded_person":"🧔","person_with_headscarf":"🧕","woman_in_steamy_room":"🧖‍♀️","man_in_steamy_room":"🧖‍♂️","person_in_steamy_room":"🧖‍♂️","woman_climbing":"🧗‍♀️","person_climbing":"🧗‍♀️","man_climbing":"🧗‍♂️","woman_in_lotus_position":"🧘‍♀️","person_in_lotus_position":"🧘‍♀️","man_in_lotus_position":"🧘‍♂️","female_mage":"🧙‍♀️","mage":"🧙‍♀️","male_mage":"🧙‍♂️","female_fairy":"🧚‍♀️","fairy":"🧚‍♀️","male_fairy":"🧚‍♂️","female_vampire":"🧛‍♀️","vampire":"🧛‍♀️","male_vampire":"🧛‍♂️","mermaid":"🧜‍♀️","merman":"🧜‍♂️","merperson":"🧜‍♂️","female_elf":"🧝‍♀️","male_elf":"🧝‍♂️","elf":"🧝‍♂️","female_genie":"🧞‍♀️","male_genie":"🧞‍♂️","genie":"🧞‍♂️","female_zombie":"🧟‍♀️","male_zombie":"🧟‍♂️","zombie":"🧟‍♂️","brain":"🧠","orange_heart":"🧡","billed_cap":"🧢","scarf":"🧣","gloves":"🧤","coat":"🧥","socks":"🧦","bangbang":"‼️","interrobang":"⁉️","tm":"™️","information_source":"ℹ️","left_right_arrow":"↔️","arrow_up_down":"↕️","arrow_upper_left":"↖️","arrow_upper_right":"↗️","arrow_lower_right":"↘️","arrow_lower_left":"↙️","leftwards_arrow_with_hook":"↩️","arrow_right_hook":"↪️","watch":"⌚","hourglass":"⌛","keyboard":"⌨️","eject":"⏏️","fast_forward":"⏩","rewind":"⏪","arrow_double_up":"⏫","arrow_double_down":"⏬","black_right_pointing_double_triangle_with_vertical_bar":"⏭️","black_left_pointing_double_triangle_with_vertical_bar":"⏮️","black_right_pointing_triangle_with_double_vertical_bar":"⏯️","alarm_clock":"⏰","stopwatch":"⏱️","timer_clock":"⏲️","hourglass_flowing_sand":"⏳","double_vertical_bar":"⏸️","black_square_for_stop":"⏹️","black_circle_for_record":"⏺️","m":"Ⓜ️","black_small_square":"▪️","white_small_square":"▫️","arrow_forward":"▶️","arrow_backward":"◀️","white_medium_square":"◻️","black_medium_square":"◼️","white_medium_small_square":"◽","black_medium_small_square":"◾","sunny":"☀️","cloud":"☁️","umbrella":"☂️","snowman":"☃️","comet":"☄️","phone":"☎️","telephone":"☎️","ballot_box_with_check":"☑️","shamrock":"☘️","point_up":"☝️","skull_and_crossbones":"☠️","radioactive_sign":"☢️","biohazard_sign":"☣️","orthodox_cross":"☦️","star_and_crescent":"☪️","peace_symbol":"☮️","yin_yang":"☯️","wheel_of_dharma":"☸️","white_frowning_face":"☹️","relaxed":"☺️","female_sign":"♀️","male_sign":"♂️","gemini":"♊","cancer":"♋","leo":"♌","virgo":"♍","libra":"♎","scorpius":"♏","spades":"♠️","clubs":"♣️","hearts":"♥️","diamonds":"♦️","hotsprings":"♨️","recycle":"♻️","wheelchair":"♿","hammer_and_pick":"⚒️","crossed_swords":"⚔️","medical_symbol":"⚕️","staff_of_aesculapius":"⚕️","scales":"⚖️","alembic":"⚗️","gear":"⚙️","atom_symbol":"⚛️","fleur_de_lis":"⚜️","warning":"⚠️","zap":"⚡","white_circle":"⚪","black_circle":"⚫","coffin":"⚰️","funeral_urn":"⚱️","soccer":"⚽","baseball":"⚾","snowman_without_snow":"⛄","partly_sunny":"⛅","thunder_cloud_and_rain":"⛈️","ophiuchus":"⛎","pick":"⛏️","helmet_with_white_cross":"⛑️","chains":"⛓️","no_entry":"⛔","shinto_shrine":"⛩️","church":"⛪","mountain":"⛰️","umbrella_on_ground":"⛱️","fountain":"⛲","golf":"⛳","ferry":"⛴️","boat":"⛵","sailboat":"⛵","skier":"⛷️","ice_skate":"⛸️","woman-bouncing-ball":"⛹️‍♀️","man-bouncing-ball":"⛹️‍♂️","person_with_ball":"⛹️‍♂️","tent":"⛺","fuelpump":"⛽","scissors":"✂️","airplane":"✈️","email":"✉️","envelope":"✉️","fist":"✊","hand":"✋","raised_hand":"✋","v":"✌️","writing_hand":"✍️","pencil2":"✏️","black_nib":"✒️","heavy_check_mark":"✔️","heavy_multiplication_x":"✖️","latin_cross":"✝️","star_of_david":"✡️","eight_spoked_asterisk":"✳️","eight_pointed_black_star":"✴️","snowflake":"❄️","sparkle":"❇️","x":"❌","negative_squared_cross_mark":"❎","heavy_heart_exclamation_mark_ornament":"❣️","heart":"❤️","arrow_right":"➡️","curly_loop":"➰","loop":"➿","arrow_heading_up":"⤴️","arrow_heading_down":"⤵️","arrow_left":"⬅️","arrow_up":"⬆️","arrow_down":"⬇️","black_large_square":"⬛","white_large_square":"⬜","star":"⭐","o":"⭕","wavy_dash":"〰️","part_alternation_mark":"〽️","congratulations":"㊗️","secret":"㊙️"} \ No newline at end of file diff --git a/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_data_cca2.json b/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_data_cca2.json deleted file mode 100644 index 39e3f0e2..00000000 --- a/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_data_cca2.json +++ /dev/null @@ -1 +0,0 @@ -["AF","AL","DZ","AS","AD","AO","AI","AQ","AG","AR","AM","AW","AU","AT","AZ","BS","BH","BD","BB","BY","BE","BZ","BJ","BM","BT","BO","BA","BW","BV","BR","IO","VG","BN","BG","BF","BI","KH","CM","CA","CV","KY","CF","TD","CL","CN","CX","CC","CO","KM","CK","CR","HR","CU","CW","CY","CZ","CD","DK","DJ","DM","DO","EC","EG","SV","GQ","ER","EE","ET","FK","FO","FJ","FI","FR","GF","PF","TF","GA","GM","GE","DE","GH","GI","GR","GL","GD","GP","GU","GT","GG","GN","GW","GY","HT","HM","HN","HK","HU","IS","IN","ID","IR","IQ","IE","IM","IL","IT","CI","JM","JP","JE","JO","KZ","KE","KI","XK","KW","KG","LA","LV","LB","LS","LR","LY","LI","LT","LU","MO","MK","MG","MW","MY","MV","ML","MT","MH","MQ","MR","MU","YT","MX","FM","MD","MC","MN","ME","MS","MA","MZ","MM","NA","NR","NP","NL","NC","NZ","NI","NE","NG","NU","NF","KP","MP","NO","OM","PK","PW","PS","PA","PG","PY","PE","PH","PN","PL","PT","PR","QA","CG","RO","RU","RW","RE","BL","KN","LC","MF","PM","VC","WS","SM","SA","SN","RS","SC","SL","SG","SX","SK","SI","SB","SO","ZA","GS","KR","SS","ES","LK","SD","SR","SJ","SZ","SE","CH","SY","ST","TW","TJ","TZ","TH","TL","TG","TK","TO","TT","TN","TR","TM","TC","TV","UG","UA","AE","GB","US","UM","VI","UY","UZ","VU","VA","VE","VN","WF","EH","YE","ZM","ZW","AX"] diff --git a/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_data_countries.json b/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_data_countries.json deleted file mode 100644 index a7472ace..00000000 --- a/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_data_countries.json +++ /dev/null @@ -1 +0,0 @@ -{"AF": {"currency": "AFN", "callingCode": "93", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABJ1JREFUSMfll19oU1ccx3+/c29yc2PaJG2TGFvTuljRdnViWx9sxSmlutmhFl+GCBvCQPBhyAR92VDwcb74IJTucQo+zVFQJs6/9M/AlqrB2cZqbbLYJrRNanL/5J7z20McbA/rbevci+fhcC/nz+d8fz/u73wvgl2TADjAdwBdAFkA6V+mcQZeAX1b4czXIOWBM5tt7cbfWXv/wPJKFiH+45XofwEjlkgIQKW+dA4EgGWcYHlgBCDGoLwcXC5DVaVsFnWdEUGhUCK/AzAiAnCHg2prcds2i3M5FOIjI8Q5hsNyIgFPn8Bcbum67cFUohJZTqfR1OSKRPjYmJzNOhTF8ngQQLjdptdr7d/v+flXtBKASEtg24MREYisSASiUUmWpVhMGhsDr7fg88HsrDI3Jw8NUUODjgTr15tqDqwcINrqlm2pnEiW5bpjx6RoVL13T9y4oUSjxsxMoLNTFAra1auOmhqIxSo/65LqPwznR0D7nqT/QjEAfHrgwMaqqvzEhDQ6KgBcXV08nY5euKBPT8cNQ6mo0Ht65Hh8QVEb123arnzcP3cbmU3AFysgiEhEq9xuYZovBwbcr14V794FAF8k4nW7zQcP+MOHXr+/LBwGALOvr7wokg9uqMIlk2SbZmmRMcYYEe3etautra19z575vj5aWGCqKlpaPLt3o89HLhdFIrnxcXN0lDwe8vqDRw9rEhQNc6o4xZAtgrdRDAB1dXWesjJ/fb2aTEZ7e9Xm5nw2+/vFi4+am2M7dz6/di2fSnn37l179qw78dK7oaGy6Kv3bADxZvlKwEQEAE6nkwsxfOmS/9AhT03N64mJ4szMms2b15486SwWnY8fc84LyWSwo0Pt7Hx6/ToyWZZkQHirHAOAqqodHR2NO3Zwzqd6e9n4eNqysLnZf+6c9/z5hYWF+bKy4uBg6qerksezoWlre8OOomyCAFy0lkmLg4lIEFWvXp1LJNZUV6dOn0aA6u5uPj2dHRhw1tdLDkcVY8bQUGFk2P/F0ZeTo0kwLicvpymNgCvMcSnU8Xj8ta6n+vsTuZxRVaU0NdUcObK6rc1tmvKLF8HW1tDBg3IwaFQF4pOTqftDf1iZZ1rcNtQ2lZ0xJoTo6u4+1d7u0HVfa6s+OGgND4OiQCCAhoHz80Qkbdni2r597tYtHgp9U3llYPYOk5gAscjONgWkJPq3+/fVEyc2hkLPbt7MFIv67dssHAafD2WZJ5OkaeVtbcqL5x99+VX/1OBwbBAksP2O7e8yCZETfVtb+8nhwxkhVgWDDNGIxRzT00KSnOvWOVta8pmM+TrnT2vXYj1nPs8wEwW+Xa1+I5oxnJyEK1cqjx+v2LdvLJWiTZv8FRUzmYzpcpUFAmGAuV+uix8u4AcZcDAwhK2iJdxOACAEMOaIx3OnTulPngQaG+c1LW9ZKgCWl69SVfPOHe3yjz6LyIFAYimWYMlGQAhCFLqe7ulBRFVRSNclRFCUWcMgImRAACCW6n6W4zKJANHBmExEmgaMEQBpmkzkYAyI3qHnAiLiHAAAEf56IADgfLlGeUX2Fv5maVfkbeF9/JP4E60nG6TGip3CAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjQwKzAyOjAwHXwFzwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo0MCswMjowMGwhvXMAAAAASUVORK5CYII=", "name": {"common": "Afghanistan", "cym": "Affganistan", "deu": "Afghanistan", "fra": "Afghanistan", "hrv": "Afganistan", "ita": "Afghanistan", "jpn": "アフガニスタン", "nld": "Afghanistan", "por": "Afeganistão", "rus": "Афганистан", "spa": "Afganistán", "svk": "Afganistan", "fin": "Afganistan", "zho": "阿富汗", "isr": "אפגניסטן"}}, "AL": {"currency": "ALL", "callingCode": "355", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAgdQTFRF3iEQ3CEQ3SEQ2iAQ2yEQ3yEQ5iIR4iIQ5SIQ5yIR7iMR7yMR6SMR7SMR7CMR4yIQ4CEQuBsNxR0OvRwOtxsNahAIcREIrxoN5SIRnBcLkRYKYw8HhhQK4SEQxh0O1CAPsxsNGgQClhYLoxgMZQ8HCwIBAAAAIgUC0B8PvxwOEAIBZw8HqRkMexIJJwYDZA8HIwUD0x8PkxYLQgoFXA4HTAsFcxEIVg0GYA4HrRoMAQAAjhUK6yMRTgwGHgQCRgoFKQYDeBIJNwgEJgYDFQMCPQkEYQ4HPAkESgsFLQcDKwYDWg0GIAUCaxAIdxIJAwAATAsGLgcDeRIJ1iAPUwwGOgkEOwkEEwMBFgMCDAIBHAQCUAwGwh0O5CIQdBEIHQQCbhAIgxMJPgkESwsFCAEBAgAADQIBNQgE1SAPPwkFUQwGSAsFJQUDFwMCGwQCRwsFUgwGYg8H1yAQRAoFVAwGmxcLDgIBRwoFyh4PWA0GjRUKWw0HMQcE2CAQoBgMMgcETQwGvBwOQwoFRQoFcBEIyR4OJAUDwx0OmRcLfRMJBwEBQQoFBAEAKAYDjxUKpBgMFAMB2SAQtRsNzx8PbRAI6iMRxx4OJQYDLAYDOAgEHwUCIQUCNggE4SIQrhoNgBMJyB4ONAgE0R8PmBcLqBkMphkMrBoM9iUS2yAQTwwGfxMJ6CMR////lzGo6AAAAAFiS0dErFdl8osAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAHtSURBVDjLY2AYWoCRiYmBkZmZkYGBhYWZhYGJiRG7OlY2dg4mTi5uNkYmHl5uPgZ+AU7s6lgEhYTZRUTFxCWYJaWkZWTl5BVYsahjYmVSVFJWUVVTU9fQ1FJTU9XW0dXjZMKikoVP38DQyNjEVE3STM3cwkTB0srahgWbicy2dvYOamqOTs4urm5qau4enioS2ExkYHXz8vZRU/PVV1PzM1XzdwgIDMKmjEkgOCQ0LFxNLSJSTS0KSPtEe5vGxGI1Ms4jOl7NSg0MrKwczBISsatj8k5KdkpRSwUqtUpVSwuzTxfDppBJICM6Mys7JxdkppVaXk5+gVOhAFY3FkkXJ5eU2pcBrVVLszXKL/eIw6qQv8LVo9JIpjATaJ6/hWhVUHVNMRO2qGGtrROt125ojFJLTVVrYmjW1mspxRqF/K1teu0dnYpd3Wo92Va9ff3KE9wnCmD1DU/HpMldVpXOamrFU9T0piqL22BNPUzs07rVpms3zQAF0ExplVlqlcGsWMORZarK7DlzjRwmqKkp9s6rUJ89n48Fh5GCspMnL/BdqOgSNn9RzIKJ0/ixGghUycEky8C3eEmu89JlfALsrOw41AFVAuHE5f1qXdN4mYFZBqc6CFjBv3LVamYGwoBJgD+WCGUglYTspBIAAL5eaGbLfsJqAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjQ3KzAyOjAw2Ns7QQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo0NyswMjowMKmGg/0AAAAASUVORK5CYII=", "name": {"common": "Albania", "cym": "Albania", "deu": "Albanien", "fra": "Albanie", "hrv": "Albanija", "ita": "Albania", "jpn": "アルバニア", "nld": "Albanië", "por": "Albânia", "rus": "Албания", "spa": "Albania", "svk": "Albánsko", "fin": "Albania", "zho": "阿尔巴尼亚", "isr": "אלבניה"}}, "DZ": {"currency": "DZD", "callingCode": "213", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAgFQTFRFAL8TAL8RH8cv4Pfi////AMATAMARH8gv4PjiAMETDLYUIKcWR6Uv5NDD/+jr//r7AcATKaMYe2oiuz8p2Sot5CQw8Sk+9DlN9mJy+qy0/vDxC7gUbXQg1ywt9Rcw9Bcw9hYw8xox8yU78yk+8yg+9VZn/MbM//7+DbcUjF8k7xsw9Bgw8hkw5yAutFM266im/dHW/dre/MzR+p6o929++73EAr4TeG0h8Row8xgw0i8sT4cbJsIw/vDy+7i//eLkOZga4CUuP5QbAMER/vX2/u/xA70TllglbXUgH8Uu4PDc+7zD92t6//j5HqoX0y4syzQrG64WH8Is25qO+5ai/dba/dzf9EVX9lpqAMITQJMb6h4vmFclAb8TH8kx3s++9j1R8ytA9DxP8hYt9lhp/vP0AMISVYQdeWwh4Pnk/bO88yM58hgv9Vdo+ZSe/NHWVYUd4Prl/sfO8yo/8hcu8hsx8y5D9l9v+q62/vb3AL4RH8gx393L90xg8hsy8yU68hUt9VBh/dve//r6HqsX0y8szDMrH8Ms25GH+XCA+7O78y1C9mFx//z84OTS//z++Zeh9mZ1Opga+7C4/uvt+7rB/ePm/dHX+Z6n93OB/MLI8yk/9llq/MrQKqMYfGoivD4p8So+9DpO9mR0+q63/vHyDbYVIacW5NDE/+js//v71zhLzwAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFFSURBVDjLY2DAAIxMzCyYgGEwKWRlY+cgQiEnKxc3Dy8fPwGFrAKCQsIiomLiEpJS+BSySsvIysnJKygqKauoqqnjVMiqoamlraOtq6dvYGhkbGKKU6GZuYWljryVtQ3YM7Z29jgUcjo4As0TcXIGBw+/kYurOnaFbu4KOjoWHpyMnl5ACW8fXxxWs/r5a+vIBwSyMgYFh4SGhUfw41AYGRWto6MQE8vAGBefkJiUnJKKQ2FauoWOjlwGJzAKM7Oyk3Oyc/Py+bEqLIArLCwqLtEpLSuvqMRrdVV1TW1dfUNjUzMOz7S0Aj3TBvRMe0dnl113Ty+uAIcHT3tfPwvLhIk4FXJOQg5w28mVuAIcLQqnTJ1GOFFMNzSaMXMWUcls9py5+BPuvPkLREQXLlq8ZCmhrCCwbDnPipWrqJe5SMiudFMIADEdVRLXGs/+AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjAwKzAyOjAwdvRgCwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowMCswMjowMAep2LcAAAAASUVORK5CYII=", "name": {"common": "Algeria", "cym": "Algeria", "deu": "Algerien", "fra": "Algérie", "hrv": "Alžir", "ita": "Algeria", "jpn": "アルジェリア", "nld": "Algerije", "por": "Argélia", "rus": "Алжир", "spa": "Argelia", "svk": "Alžírsko", "fin": "Algeria", "zho": "阿尔及利亚", "isr": "אלג׳יריה"}}, "AS": {"currency": "USD", "callingCode": "1684", "flag": "data:image/png;base64,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", "name": {"common": "American Samoa", "deu": "Amerikanisch-Samoa", "fra": "Samoa américaines", "hrv": "Američka Samoa", "ita": "Samoa Americane", "jpn": "アメリカ領サモア", "nld": "Amerikaans Samoa", "por": "Samoa Americana", "rus": "Американское Самоа", "spa": "Samoa Americana", "svk": "Americká Samoa", "fin": "Amerikan Samoa", "zho": "美属萨摩亚", "isr": "סמואה האמריקנית"}}, "AD": {"currency": "EUR", "callingCode": "376", "flag": "data:image/png;base64,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", "name": {"common": "Andorra", "cym": "Andorra", "deu": "Andorra", "fra": "Andorre", "hrv": "Andora", "ita": "Andorra", "jpn": "アンドラ", "nld": "Andorra", "por": "Andorra", "rus": "Андорра", "spa": "Andorra", "svk": "Andorra", "fin": "Andorra", "zho": "安道尔", "isr": "אנדורה"}}, "AO": {"currency": "AOA", "callingCode": "244", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAbZQTFRF/wAA/wEA/wIA/wMA/wwA/x0A/wQA/20A/7sA/+gA/ycA/0MA/60A//IA/6UA/4EA/zMA/woA/x8A/yMA/8AA//8A/18A/xwA/2oA/6YA/zcA/wgA/7YA/6IA/zgA/AAA/AEA/QAA/BkA/MsA/P8A/GsA/B4A/fIA/98A/QwA/2kA/zoA/wYA/6kA/nkA/gAAwwAAwQAAxggAxAoAxQkAwwwAxAQAvwAA3HMA/9QA3C0AxAIAAAAAAwIAjIkAOzsAAgcAAAEABAQAc2kAXmAAAgAAy8gA39UALyoAAgEABAIABgQAsaQAiH0ABQMAZl4A3c8AcGcADQwAEg8A8OkA1csACQcAAQEAAgIAQTwAzsEA//EAuKwAPDgAmZEA49UAhnsAGBYAEhEAYVsA280A4dIAgHcA2soAWVIABQQAAwMAdm0A388AZV4AKicACgkAWlMA0MIA798AzL4AYFkAAQAA0s8A/vgA5NIA8uAA7tsA7+MA+e8AVk8Ar6MA8OAAf3cAGRcAUkwASUMAGxkA9eMAbGQAcWgADAwAU00AsKQAaWIABQUAOjYAwrUASUQAFBIA////QnDRNwAAAAFiS0dEkQ8NvpoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFDSURBVDjLY2AYxoCRiTh1TMzEmsjCysTGSNg8BnYOTi4GwrYzM3Dz8PLxCzAQNFNQiEFYRFSMgQ2/hxnEJSSlpBlkZOWwGSkPBwqKSsoqqvLyauoamvIK8mgAxUhmLX5tBh0GXT19LKFkAAeGRsYmBqYGZuYWllbm1gZogMEGBmxt7OxtHBxtnGycRV2AJBpgcIUBWxs3dw8bTy9vGx9fGz9XdIAw0cnGXzQgMMjGJjgkNMwmHMNEBDPCJjIqOibWJi4+AUMZQmG4TWISUG1ySmpaeoZNJm6FWTbZObl5+TYFhUXFJTYRWTitLrWxKSuvqKyqrqmtq29oxOPGcJum5pbWtsb2DpvOuq7ufHSlDMic8J4IoDuzbGx6+/qBjsGlMAvosPAIp3CbrAibCVk4rYbaDwspG/wKMXQQVEi0iRQoBADlxK8dqvctggAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1MCswMjowMNHWBVEAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTArMDI6MDCgi73tAAAAAElFTkSuQmCC", "name": {"common": "Angola", "cym": "Angola", "deu": "Angola", "fra": "Angola", "hrv": "Angola", "ita": "Angola", "jpn": "アンゴラ", "nld": "Angola", "por": "Angola", "rus": "Ангола", "spa": "Angola", "svk": "Angola", "fin": "Angola", "zho": "安哥拉", "isr": "אנגולה"}}, "AI": {"currency": "XCD", "callingCode": "1264", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABrFJREFUWMPFlmtwVOUZx3/vOSe7m91AgNyEIglUyFhBDYgNVShURS4fZNSBOg0BZcZYpGg6iNOxU6fTi51pY5FEZpgCOuooKCIwBZSbtrFGMNhEQwghQskmIZtsVnbZ69lzztsPu7mQGNT9EP4z75zZM8857+88/+d53hVfLV0SyX1mo2YV3cmLm46LypeO4vMFAQUhQErJzNlT5P4/FONdsQzD70fNyCDv7fd48E91fPqfZhRFAcCSkD85h/XLJrOk4zjiXCP2NU+ybItbrW/oECBISvLNGnJf8WQVaB3PPaeFK17QNpbOUN9463F17rxCVVVRpZQqSBXQEktedZVSaiA1yzI1e7pNW7aoUKtaqGqLa7ZpLoemZW3aoh3VpmnuSwExaF8xzFIGL21Nba4o/fGD3H/qY0IflfCTsjJ2v17K9p11VG0+Rke7l+ElAUHRzHyeuGs0d50/hvOcn8ynnqYxZzoVf6/m0IEviMXiDMje95KAxyw1zSbunj2Jsqkhpp8+Qkb+jeSWl9OojefPf/kAjzfE27+dRc+KZRj+AGqGi9xde1m7/Tw3OWOsSGtmzOlPyVi8BP99D7NtXwuv7qimp8c/IBmpScyd/6JlWVLE4yZjszJYPm88C7pqsM6eIad0JeKexZw64+UWvY32B5ZiBgIoLhcFBw7hS8/GvvcNZE83WWVPcDZtPJWVx6mvc4MEoUAsatDcdAldN1ID7PqwOi4lmpQS07SQCEZl2OBKAGkYaGPHoqU7CNbV07puHVYohJKezo2bX8I141aMSBjFkY5ISyMYiKDrBoqSsFMIcHeGeGjDUdo8wZRsFnWZmXGJ1ERf7SY6F5F8mUw0ljRNrHA48VsIFKcToaokW72vYMQACIGkQ7pYE7yHDsvF8M07vDQjEOjf4LtKSqxQ6Nu/HjCFRGKlZC+AprpcQwBlb+bEAEtMEysW64tVHA5Q1SFxA00USFSZjoiKVJKXAMzb9V7CmqRTihA4XbaEfUkwRVMJf9mAu7wcKxxGcTiYWFGBa2YRVtxAxuOJ2DQbobCOaRgoQkFRIO4Jo2z8CLpSq0HtoRfqALAsyYQJYygrm8fCuYXEmpr4eudO7JMLiMxfiksqCE1LPKWqpP1oOpd/eBt52U7ijafxbt+OSE+nfup8th1y09bqQ1UFesyg26+nBAeg1HzcxMmaZka7bDy1/mcsmJWHt7IS96Or8LV2srVWZ/2GvQSvRPucFALCYZ1fPfkmv39+P77M8eSuXoXR0U7B1udZNdbNGLuk9uRXfF7bkhzUKVo8bdoE1q5bwOrSYmynP6dtdTlmJErzvauoPKlz5Nh/ub1oEoKpVz0oBLS3+9jzzgkOHvySXz+ziAc2vcy4fx/DsWULfx0zmgO/KGbrh15aL/akDvj+4aeZZI/R/bc/4vngMKE597IrPo1XtjXR1enrp+HqQzNpAACnTv2PNat38O6SW9nw7CLueO11/K+9yiPvH2BK0c2s7dH4OpjaoNaMT6r5ZPNmLKHivq+MquM9fPbZCSzTSgJY6LpBR3cYn3RhYqLKDOLdEXQ93gcajRrs2VNLdXUzjz52NytXlpBWeAeOqlewRxXAnhKgmGQvjVt6VBM5E/BFJaFAiMFnp82ukTfWgdXdhbQshBAoObl0Xda/ob4kigLZ2aNJdzkwYzqdnX6M5Ci02y2ktNB1gRCQlwe6LvD5EnPI6ZRIqRCJKL2uPR4HoYHFtTtNDgL/LvF9eei7t3Spg5KSGbS1RRjlNLmzeDpR3U5NTTNGPEph4RgqKmqprg4DAq3/Zd82BsQwm14rfih0VlYmy5c/gqJkwMXd4DkM+fcz55Y54JpCRI9SVdUAhAF6AUdSSa/NGJx9ExQbhDwQDYBrKtKysCyzL3qEAQWGYSLDlyDoAc0JN/0c4kG4uA9GFWA6JhKL9Xd86v8kU1Tgio5hxMDXAFMehtxiyJ4FRhROPEu0+wxXgia9JTLiGfR0RglJF3YzDBfehZa3YOJC+MECCLfjC0q8Xv36ZbC1VcftvgTZt4EZgezbYXRBohZn/oam82G83f0Wj3iTdHXBwSNfIH5ZQqz4p6BqEPVBWiG2sMW+f9ah6/0TYMQBpVT5x456ds+YxbkbZqNYIBmHUMdxc8NBWvZfYKCxI24xwMVGA3/lPmyX2/BLCEhwes7iefko3q6r56cKs3430qASwZWWIDf4zzOuMINRPR0om97B/S8fchBK8qi7HgMbVMUiN18hHhf0tFtIOfT0uS5gvTIthUsX4FpH7XWpwe+j/wPBL+O9kmJ9fgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDozNiswMjowMHRpOewAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MzYrMDI6MDAFNIFQAAAAAElFTkSuQmCC", "name": {"common": "Anguilla", "deu": "Anguilla", "fra": "Anguilla", "hrv": "Angvila", "ita": "Anguilla", "jpn": "アンギラ", "nld": "Anguilla", "por": "Anguilla", "rus": "Ангилья", "spa": "Anguilla", "svk": "Anguilla", "fin": "Anguilla", "zho": "安圭拉", "isr": "אנגילה"}}, "AQ": {"flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAVBQTFRFOn3OPH7OO37OO33ONHnMM3nMN3vNPH7POXzOM3jMXJPWWZHWRYTRMHfMNnrNOX3OOHzOutHu+fv9+/z+8/f8z9/zTorTPH/PMnjMs83s/////P3++vz++vv+hq/hUo3UMXfMP4DPNXrNR4bR7PP6/f7//v7//f7+/f3+xNjwX5XXNHnNMHbLap3aj7XjKHHKP4HPQIHPZprZ+/3+mr3mK3PKPX/POHvNQoLQ6/L6xdnxUIvTJ3HJrMjqwtfwW5PW9/r9krfkirLiytzy6fH6RIPQy93y/v//3Oj2L3bLydzy+Pr9p8XpSYfSPoDP1uT1gKvfLXXLYpjYcqLceqfe9Pf8ocHoLXTLL3XLa57afqrfLnXL0eH07/X7nb7njrTjtM3skLbjt9Dt4+z44uz4LHTL9fj8bp/bPn/PMnfMr8rrpMPoKnLK1eP1e6jeMHbMsWd+rAAAAAFiS0dEGnVn5DIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFxSURBVDjLzZPZW4JAFMUZUBwsREuDLlqkgZq2mEll+2JppVmWtu/7+v+/BVp9Kig+9XWef3POuXdmCOI/CSGyE4wibXaacFiyJGacXd2YdSHU3o9ze3p6vT5nH8Pw5iiuhiGhH0QA8AcGBiXGLB8PBSnEUXRoGGRQwgpExOhIzEii+OjYeGwiIU2CAroUJQlTKdXYbXpmNj2nsvML1WRdIixKJi2RsLQMK6trAfiRAusZbLIWOrQBm/AdrEuGbM5lMg6Jg1vgF3857dT2Tp42khRX2NVc6iVCcU81KbkPTQpDKc4bQJ49ONRLNjiWK7oj5hpIx9FxWGlyPDnVQFxQG0kknWnTaJJrfATStUU2XzqyFc/r1qMlX+Sqs3DNNUk2c3lVvr65vdP5CPiEVq/SQVfuKymBLUFSTsLDo73lmyRVVX0qPL9odjK8vqlEO1E44cl636MfQcvv4P6k80QuTllx2vyEi+B5a04v2xH1R/oCqT03XvcrJGcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDMrMDI6MDAslB9SAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQzKzAyOjAwXcmn7gAAAABJRU5ErkJggg==", "name": {"common": "Antarctica", "cym": "Antarctica", "deu": "Antarktis", "fra": "Antarctique", "hrv": "Antarktika", "ita": "Antartide", "jpn": "南極", "nld": "Antarctica", "por": "Antártida", "rus": "Антарктида", "spa": "Antártida", "svk": "Antarktída", "fin": "Etelämanner", "zho": "南极洲", "isr": "אנטארקטיקה"}}, "AG": {"currency": "XCD", "callingCode": "1268", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAr5QTFRFBgYGBQYGBQUGAAAABgUADQsAAQAABwYAKCEAAgIALCUAWkwAJyEAFBEANgAABAMAg24ANi0AY1QAqY8AAwIAHRgAmIAAGRUALwAAuAAAJgAACggAGhYAAQEAg28AuZwAFREAnoUA3boAIx0Aup0ACAcAIQAAsgAA5QAAmgAADgAAemcAalkAW00A+9QAmoIA1bMA9s8ApYoA99EAmYAACgkAXE0AHhkACwAAkwAA4gAA4QAAbwAAAgAAMysA37sAspYAdmMA9M0A/NUA/dQA/9YA/dYA/9gAqo8Aq5AA8swAZlYAZwAA3wAA4wAA1AAARQAAGBQAemYA/tcA9c4A/NQA/9oADgsAFhMAEg8APgAA0AAA5AAAuwAAIwAAYlMAnoQAkHkA78gA/9cA/dUAsJQAupwAmYEAHgAAtgAAlwAADQAA+dMAn4UAHxoACgAAkAAA4AAAbQAACwgAXEwA6MMAm4EAGRQAZQAA3gAA0wAAQwYRABEtAxMsJjAsXl8sk4wsuawsz74s08Is0sEsvrAsmpIsZ2csLzgsBhYsABEuPAcTzwAAug4kIUzHAFvuAlvrD2brGG3rFmvrFGrrFWvrF23rEmjrBFzrAFruHU7MtBAqlSJZDF3yAGL/AGH/AGD/CV71jSVh4AIEajSIAWH+YjeQ3gIG0ggUQUa2Oki+zgkYuBMxIFTbG1bgshU4kiJaC1zzAF//AF7/CF32iyZj3QEFikuKbKT+bqX/bKX/hlGT3AIH5xYV+Lq5////+cHA5xoa6DMz+93d/OLi6Do67V1d/vT0/vb27mZm4gUF8o2N//7+85aW4wgI5BcX97u7+MPD5Rsb6DU1+9/f/OTk6Tw87WBg/vX1/vf37mlp4wYG8pCQ85mZ4wkJ5RgY+L6++MXF5R0d6Dc3/OHh/Obm6T4+7mxs73R04gEBlK46QQAAAAFiS0dEv9Pbs1UAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAHtSURBVDjLY2DABIxMWAQZmDEBCyuQYEMTRFfIzsHJzMXNzMzDi66QD5XPLyDILCQsIiomjiouwSApheBJy8gyy8krKCopi6moMosjqVVTZ9DQ1ELYq60jraunb2BoZGxiaqZoDpewsNRgsLK2sYUL2Nk7ODo5u7i6uXt4ennDhdl8fP0YrPz8A6DmBZoyBwWHhLqCQGiYQzhLRCREJio6xorBykojNg7MVY13SEhMSnaFgJRUsbR0HrBERqaGFUihlUZWNiT8dJXcXeHAPSc3DyycXxBjBVFoVVgEdiZbcUmpezIMuJWVQxxYUekHU+hXVV1TU1tX39DY1AwHLa1t7R2dNV3dPSADIQqtNHr7+idMnDRp8hQkMHXapOkzZs6arWGFUGilMWfuvPkLMMD8eQsXxVghK7RavGTpfGxg2XI/VIV+K1bOw1Q2b9VqqIFwhVYaa9ZiqJy3br2GFbpCq5gNGzdtRgGbtmyNscJUaLVt+46dKGDX7j1+2BRa7d23HwUcOIgkiazQ6tBhZHVHjlrhUmh17DhC3YmTVrgVnjp9Bq7w7Dk8Cq3OX4Cpu3jJCp9Cq8tXIOquXrPCr9Dq+g2Qupu3rAgpvH0HpPDuPYIKre4/2L//4SMrwgqtHj95+syKGIVWz1+8xBQEANFbahfUdCzxAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjI4KzAyOjAw6PxCLwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDoyOCswMjowMJmh+pMAAAAASUVORK5CYII=", "name": {"common": "Antigua and Barbuda", "cym": "Antigwa a Barbiwda", "deu": "Antigua und Barbuda", "fra": "Antigua-et-Barbuda", "hrv": "Antigva i Barbuda", "ita": "Antigua e Barbuda", "jpn": "アンティグア・バーブーダ", "nld": "Antigua en Barbuda", "por": "Antígua e Barbuda", "rus": "Антигуа и Барбуда", "spa": "Antigua y Barbuda", "svk": "Antigua a Barbuda", "fin": "Antigua ja Barbuda", "zho": "安提瓜和巴布达", "isr": "אנטיגואה וברבודה"}}, "AR": {"currency": "ARS", "callingCode": "54", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAexQTFRFAMT/AMT+BcP4FsPlFcPnFcPmFsPmBcP5Icz/JMz7OsjUXsiqa8icacidasicbcmbOsjVI8z84vj/4vj83+zH6Oag6eF959505NdW5dhW6N906Oim3+3L///////++/PM+uuh995c9txV9tc38tQ19NU19tg59t1X+N9g+uqd/PTP/frs+OmZ9+R+9dpG89Ux8dZF8tY/89Y/8tdG89Uw9+SA+Oqd/fvv+/TR9+aM9+Fo9dc08NZN8s8c88sA88sB89Ae8NVJ9NY09+Fp9uaN+vTU+vPP9+WJ9NpN8tU47tE76cIA6cMA79I98dQ589pU9uaQ9+WI9NlH8dU+79E1/NMA+NAA+dEA/dQA8NM68tU99NhD9uSE+vPO9+eS9+Fr9NUv7tNF+NIN8skA9csA+dMP79ZO9dYw9+Bn9+eP/frr+OiT9+N29dlD8NRB9NQr9dUv8dZJ8tU39dpH9+N3+OiR/Pnp//79+/HA+uqa99xR9tpK8dQ88dQ79dUw9tlE99xQ+uqb+/C+//783Pf/2/X41+e25OWa5uB85dxo49VL5NVL5dxn5+B65OSV1+e42/X5G8r/G8r+Hcn4RsjHasibc8iRc8eMcseNc8iQbsmXSsnDAcT+DcPvJMPVJcPVJMPWJ8PSEMPtAcT96IDw8AAAAAFiS0dEHesDcZEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADxSURBVDjLY2AYBTQBjEzMLKxs7IwYEhyogJOLm4eXj59bQBBNgkEIBQiLiIqJS0hKiUnLCKPKMMgiAzl5BUUlZRVVNXUNTS0UGVlUhdo6unr6BoZGxiZ6pmbmeBRaWFpZ29ja2Ts4Ojm7uOJR6Obu4enl7ePj4+vnH4BXYWBQcEhoWHhEZFR0TCwehbFx8QmJSckpqWnpGZn4FGZl5+R65uUXFBYVl5SW4VFYXlFZVV1QU1tX39DY1IyqsAUFtLa1d3R2dff09vVPQJVhmIgCJk2eMnXa9BkzZ82ejCoxESPu58ydN3/BwkWLBzpxDlcAAHiob+kULOLoAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjIwKzAyOjAwNNFndgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToyMCswMjowMEWM38oAAAAASUVORK5CYII=", "name": {"common": "Argentina", "cym": "Ariannin", "deu": "Argentinien", "fra": "Argentine", "hrv": "Argentina", "ita": "Argentina", "jpn": "アルゼンチン", "nld": "Argentinië", "por": "Argentina", "rus": "Аргентина", "spa": "Argentina", "svk": "Argentína", "fin": "Argentiina", "zho": "阿根廷", "isr": "ארגנטינה"}}, "AM": {"currency": "AMD", "callingCode": "374", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABtQTFRF/wAA4AAfHwDgAAD/HxTg4JEf/6YA/6UA////f9mp/QAAAAFiS0dECIbelXoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAApSURBVCjPY2AYykAQC2BQwgIYjLGAwSjoggUwhGIBDGlYAEM5FjAIBQFxsYIpc0RtnwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNTowNyswMjowMLNTXoUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MDcrMDI6MDDCDuY5AAAAAElFTkSuQmCC", "name": {"common": "Armenia", "cym": "Armenia", "deu": "Armenien", "fra": "Arménie", "hrv": "Armenija", "ita": "Armenia", "jpn": "アルメニア", "nld": "Armenië", "por": "Arménia", "rus": "Армения", "spa": "Armenia", "svk": "Arménsko", "fin": "Armenia", "zho": "亚美尼亚", "isr": "ארמניה"}}, "AW": {"currency": "AWG", "callingCode": "297", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAASxQTFRFM5nMOZzON5vNMZjMaavPVqTOM5zPmoWbe5CuMJrOMpnMP5/PuFFbnm2ANJ3QMJnMV5vEyigruUdQQp/OMJrNMpzQOaDSg4unzw0NyiMmapu/N5/RMZzPMJnNOJzNTJ3KaZK2jXuUrllmyCotzQAAzQUFxDY7qWBvhoCbZJW6SJ/MNpvNUajUlqG5v11l0CUm0gYGzgAAzAAAzwAA0gsLzTAzt215h6rHQ6LROJvNR53LZJa8h4OduU9YzQICzQwNrWJwfommW5rCQZ3MNZrNL53RcZO1zxMTxi8zVaDKL5vPMZnNMZnMUJ3JxjM3slNfPZ/QO57QsWBtk3iPMprOjZewcJq8VqnTSaPQMpjMNJnMMZjOTKWz6vUVuNtHNZrKudxG6vQVS6W0////79jqOwAAAAFiS0dEY1y+LaoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAC5SURBVDjLxdDVbgJRFIXhXQaH4t4O7lpcirtLKa7l/R+CPsHMIiHhu/6zss8hetSbAOsYoYiBQrFEKkM6uUL5rkJCtUar08t55wxGk9litX18spyt3eF0uT1enz8QDIUjHHvRWDzxlUyl0plsLl/g2iyWvsuVaq3eaLbaPEeynW6vPxiOeJ9D48l0NufP/i1+lkako9/VmoVCZrPdQSHtseyZDiA6gugEojOILiC6gugPRDcQ/o+vcwdMtZUvzqE+HgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNTowNiswMjowMBUkVTEAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MDYrMDI6MDBkee2NAAAAAElFTkSuQmCC", "name": {"common": "Aruba", "deu": "Aruba", "fra": "Aruba", "hrv": "Aruba", "ita": "Aruba", "jpn": "アルバ", "nld": "Aruba", "por": "Aruba", "rus": "Аруба", "spa": "Aruba", "svk": "Aruba", "fin": "Aruba", "zho": "阿鲁巴", "isr": "ארובה"}}, "AU": {"currency": "AUD", "callingCode": "61", "flag": "data:image/png;base64,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", "name": {"common": "Australia", "cym": "Awstralia", "deu": "Australien", "fra": "Australie", "hrv": "Australija", "ita": "Australia", "jpn": "オーストラリア", "nld": "Australië", "por": "Austrália", "rus": "Австралия", "spa": "Australia", "svk": "Austrália", "fin": "Australia", "zho": "澳大利亚", "isr": "אוסטרליה"}}, "AT": {"currency": "EUR", "callingCode": "43", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABtQTFRF3wEB3wAA3gAA4RAQ/ezs//////7+/e7u4RERPLHzTAAAAAFiS0dEBfhv6ccAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAA5SURBVCjPY2DABgSxAPoJYgVKWACDMRbA4IIFMIRiAQxpWAB2lRQKEm9RORbA0IEFYA+QQRib2AQBXQZrlUZGX0YAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTYrMDI6MDDZjlWvAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjE2KzAyOjAwqNPtEwAAAABJRU5ErkJggg==", "name": {"common": "Austria", "cym": "Awstria", "deu": "Österreich", "fra": "Autriche", "hrv": "Austrija", "ita": "Austria", "jpn": "オーストリア", "nld": "Oostenrijk", "por": "Áustria", "rus": "Австрия", "spa": "Austria", "svk": "Rakúsko", "fin": "Itävalta", "zho": "奥地利", "isr": "אוסטריה"}}, "AZ": {"currency": "AZN", "callingCode": "994", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAitQTFRFAJnMAJjMAJjLB5zNFKHQGKLRDp/PApnMAZjMJajTd8jktuLx1O/42fP7yez3m9ntTLreC5/QAJrNAJnNB5vNZcLg3PH4/v////7++uTk77a16JiX6ZaV4qqsnrjKJabSGY26GIy5GY+8fcnj9vv9/////PT056Cg005OzDQ0yzAwzTMz1UhHrHmEMZC4F426sz9Fsz1DxoOJ9vj5/fX14YeHzDExzDMzzDIyvUBEtkhPtkpQzjIyzTEx0kJC9dLS6aenzTU1zTc30D09zC0t3nRz4Hx80D08zzc23HJy/vr6+uvr1VZWyzExzjk55pub4oyM78DA8cjI4YiIzj09556e8MXFzTg4yy4u3np6/fn5/v39/vv74YaG7LS06qyszjs73Xl58MTE/vz8//398snJ335+zz097LOz662tzjo6225u7bi4776+zjw8339//ff34oqK/fj4++7u1ltb5JWV3n198MHB3nl55pmZzz4+zjIzzTEy0T9A88vM67CwzTY2zzk53G5v3nZ3zzg5zjY2sz8zsz0xw3tv9PXx5JKSvEI5sz8ytUY6tUc7GY0zGIwzGI40b8KJ8Pnz666u1VhYzjY31lBRo4JnK5I/GI0yAJozAJkyBJo1Vbt3z+zZ/f79/e3u88TF7aep7KWm3ravkLuRHaVKAJkzAJgxGqNIZMGDpdu3xenSy+3YuebKiNKjPLRmBpw4AJgyAJgwA5o1DJ49D58/B5w5hA1wTgAAAAFiS0dEJcMByQ8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAF/SURBVDjLY2AY3oCRkShVTMwsrGzsTITUcXBycfPw8vELCGIYK4QEhEVExcQlJKWkZWTl5IVQAYMCAigqKauoqqlraGppaevo6ukrKiJJMhgggKGRsaqJqaaZORBYaFsaWFkjSTLYwIGtnb2qqoOjhTkYODm7uLq5uHvAZBnM4UDL00vV28cXyvPzDwgMClYPgeozR1LoG6qqGhYO5VhEREZFR8fERmBRaB6nqhoPFbdISExKVlVNSU1Lt8BUmKGqmgkzICs7J0ZVNTrXMw9Toa+/qmpQFowXkV/gFRNViM1qrewi1eISLZiRpWU55RWVVXCF1XBQU1unqlofDpVpaHRpanZpaYXJMrQhQHtHp2pRlxPYTDPH7p7ePiRJhn4EmDBx0mTVoilTw818p02fMXPWhAlIkgyzkcCcufPmL1BduGjxkqXLls+ZjQIYVqCAlatWr1m7bv2GjZs2o0qsQFO4YvOWrdu271i5ZcUKAgqBYM6cFVgAwwoiwVBQCAA1lnoBhKT9DQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNToyMiswMjowMKNOdl8AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MjIrMDI6MDDSE87jAAAAAElFTkSuQmCC", "name": {"common": "Azerbaijan", "cym": "Aserbaijan", "deu": "Aserbaidschan", "fra": "Azerbaïdjan", "hrv": "Azerbajdžan", "ita": "Azerbaijan", "jpn": "アゼルバイジャン", "nld": "Azerbeidzjan", "por": "Azerbeijão", "rus": "Азербайджан", "spa": "Azerbaiyán", "svk": "Azerbajdžan", "fin": "Azerbaidzan", "zho": "阿塞拜疆", "isr": "אזרבייג׳ן"}}, "BS": {"currency": "BSD", "callingCode": "1242", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAYlQTFRFAyorCJCVCs3VCtHZCs7WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0hKARESBF5iB7S7CNDZCM/XCM7WAAAAAAEBASMkBX+ECMXMCNHZAAUFAj5BBp6kCM3VCNDYARERBGBjB7a9ASUnBYKHCMbOAAYGA0JFBqGnARMUBGRoB7nAAikqBYeMCMjPB87XAAgIA0dKCaapHNHGHtLFHNDFBhYSWnMkv9Ak2egk1uUk1eQkAwIAOzMAsZ8A+eQA/+wA/+kACwoAX1cA0sAA/usA/+oAHxwAi38A69cAAgIAQTsA1sMA/+sAHhsAiHwA7dgACgkAW1MA0L4AAQEAMi4AqJkA9uIAFhMAe28A5dAAAAQFM0IUqq8W5eYW6OgW5uYWAR8gBXp+H8a3LtW4K9K4AAsMA1RXB7C2B8/YBs7YAAICAjEyBpGXCMvTARYXBGxwB73FAAcHA0ZJBqasASYnBYSKCMfPAQ8QAAQEAjk8BpqgAR0eBXd8CMLKBqeu////BjZAKQAAABF0Uk5T5O3t7e309fb3+Pn6+/z9/v4TFSGkAAAAAWJLR0SCi7P/RAAAAAlwSFlzAAAASAAAAEgARslrPgAAASBJREFUOMuN1GVTw0AQgOHFi0ObZAMECZS2uLsUioVixd3d3f2fE2DKNOw1d+/nZ+buZm4XICY2Ll4gSMh2uiRZ4QaJqObk5mkiEDG/oLBI58Ek/K64xK3JIhDVUo9XE4GIvrJyxeZ8cOBfFZVV0c+PhKhW19RKdewsELG+obGpuYUVJKO11rb2js4uGoHY7e/pDfSRIAVJ/QODQ4YIRBwOjhhCEEfHxif+wVQmDE1OGQJwemaWXBLSCJubX1ikz6ZwaXklQN5M4era+sbmFiNIt7jtnd29fWYWeHB4pB+fsIuAp2fnF9E/JGSE3eXVtd0XD8Ob2zvZdmh+oXr/8ChxpjDTdE9O3gz+wOeXV+5UmxDf3j/4e8KEWZ+6wOZRlC+yuq3di27g4AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1MCswMjowMNHWBVEAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTArMDI6MDCgi73tAAAAAElFTkSuQmCC", "name": {"common": "Bahamas", "cym": "Bahamas", "deu": "Bahamas", "fra": "Bahamas", "hrv": "Bahami", "ita": "Bahamas", "jpn": "バハマ", "nld": "Bahama’s", "por": "Bahamas", "rus": "Багамские Острова", "spa": "Bahamas", "svk": "Bahamy", "fin": "Bahamasaaret", "zho": "巴哈马", "isr": "איי בהאמה"}}, "BH": {"currency": "BHD", "callingCode": "973", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAP9QTFRF//////7+/Obo85Wc6D5M4QUR3gAA4AAH4QAS4QAR/Onr9KOp5zZD4QAN4QES/fHy97i97nF75SMy4QAP+c3Q6klX4QQP3gAB4AAK/fDx97u/73V+5i484QMS4AAM+tLV6UdT/e7v7nJ75zJA4gsc4QAQ//39+MfL5zVD3gAC3QEA3wAB97m97m125iw64ggZ+tTX6UhU/e/w73iB4gYV4QAO+c3R4QMO4AAL9rK37Wlz5R8u/vP09a6z6DpH+9zf8H2G4QUT4AAJ+93f5zA+4QMQ3wAA9ait5zdE97e87nF65SMx//7/9rO49auw6DlG//z9/Onq85qh6URR4QcV6RryvgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADHSURBVDjL7ZTFEsJgDITzI8Xdi5biRYt7cXfe/1koXDhBcuDInr/ZzczuBEAVY6DR6vScwWj6KHjLbLHa7ATQ4XS5PRjIwOvzc4EgITrEhyPRmAkHn67xRAy/kQl8UkylMccMZHP5gkSKFoqlcoV4Y7VGuFHm62KjiToyaPnanNQhVSh0e320mZfpYDhCu2YwVsSJNEWjGcyU+WKJgy9WXq1JMwtttjvizKT/zD6Bv5/ZHuQDbWYqeTx965qpypzhcr3dvz6pB+eQNNljOxxrAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjUzKzAyOjAw4D4fzAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo1MyswMjowMJFjp3AAAAAASUVORK5CYII=", "name": {"common": "Bahrain", "cym": "Bahrain", "deu": "Bahrain", "fra": "Bahreïn", "hrv": "Bahrein", "ita": "Bahrein", "jpn": "バーレーン", "nld": "Bahrein", "por": "Bahrein", "rus": "Бахрейн", "spa": "Bahrein", "svk": "Bahrajn", "fin": "Bahrain", "zho": "巴林", "isr": "בחריין"}}, "BD": {"currency": "BDT", "callingCode": "880", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAKtQTFRFClwSC1sSDVsSDlsSDFsSBF4SAGESAGISBF8SIVMSUkERbTYRUUERL04Sqx8Q8wQP/wAPqR8QLk8SZzkR+AIP/QAP+QEP+AEP9wIPZDoR+wEP+wAP/AAPAV8SMU0S9wMP+gEP9QMPAl8SqSAQpSIQI1MS8AUP/gAP7gYPIFQSBV4SC1wSTkIRbjYRajgRMU4S9gMP9QQPZjkRYzoRqh8Q8gQPLU8SIFMS////SO84FQAAAAFiS0dEOKAHpdYAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADSSURBVDjL1ZPJEoIwEEQhLAMoMqIQhai47/v2/39mtCzNIcRUedE+v5qu6ekxjH+TSSyLmB8xmzgugOsQW80RD/ygUqn64BElB2EtQq6oHgNRcY0mJimlaYKtdjnJOcxozrhymmEpaXe6PVawp4qkHw/kGxF3iJS9RHHkSkeazngyzd9gjrP5YikdGGDKBBW4ko60YC063703YH0DaltrL6MdDw98uxMD35cFzk94EE94VB378C7FUV2f8PSo2Xl4AXUhn8W9fiiu/isY2s/1q7oBVsgdRZ6IiLwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MjErMDI6MDCSpmzCAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjIxKzAyOjAw4/vUfgAAAABJRU5ErkJggg==", "name": {"common": "Bangladesh", "cym": "Bangladesh", "deu": "Bangladesch", "fra": "Bangladesh", "hrv": "Bangladeš", "ita": "Bangladesh", "jpn": "バングラデシュ", "nld": "Bangladesh", "por": "Bangladesh", "rus": "Бангладеш", "spa": "Bangladesh", "svk": "Bangladéš", "fin": "Bangladesh", "zho": "孟加拉国", "isr": "בנגלדש"}}, "BB": {"currency": "BBD", "callingCode": "1246", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAATtQTFRFpYo3poo3pYo4poo4mH8sExAtZ1Y4aFY4noQ3n4U3qY03p4s3AACdAQGc/9gA/9QA/9UAAACeAQGf78cAAACLAQBs/9cA/9YAwKEAAACRAAArHhkBd2MA3rkA+c8A17MATD8AAACfAABwAAAIAAAAMCgAv58A068AXk4AAgKZCQgAqYwAwqEADgsAqIwABASdFREABgUAtpgA/9oA1bIAFBEAAgGcV0kAdmIA1bEAWEkArJAAAgEAPDIA9cwA1rIArZAA6MIAJiAAFBAA2LMAXk8ArpEA2bUA/9sAlX0Ah3AA2bQABQQAZ1cA1rUABQUA2LQAFRIAMSkAfGcADAoAFhIA/9kA5b8AJR8A7sYATUAAIBsAAgIA/dMA7MUA68MAvZ0ADwwA0a8AEQ4AEg8AHBcA9MsAwKAA////G06DPgAAAAx0Uk5T/v7+/v7+/v7+/v7+1swwKwAAAAFiS0dEaMts9CIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFESURBVDjLzdRHW8JAEAZgULBQ4oaSpdclAQSpi6BAJHQQREB6r///HwgefDgkgSPfdd/LzM6MRHUStYR4+g8hVZ++Sa4LAnAGkpqbIwRa7VEStxqSH5I6vYyCB2EwHDykZHodyQ+NJrPFCm12h8Nug1aL2WTkhyqny408NONFyMvQHuR2OYWK8cmR/xkGgsEAfPEjuU+w6tBdOBKNxTGOx6KR8H1IECYeXlGSSGGcIpLo7TEh3EfFezqTZTFms5n0h0Kk4cocl2cLGBfYPJdTikApVUSlMsblEipSol9YqaJaHeN6DVUr4pD+bDQxbja+aHEYa323O91up/3TionCXn+A/jLo98ThcMSNJ5MxNxqKQ2I6my+Wy8V8Nj03uIBYrdcrApyfcMhsNgy8YBUgu92yl0Cw2+934JLlAhQFrv4A8MJfphFdqP62DCgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDQrMDI6MDDpMyHcAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQ0KzAyOjAwmG6ZYAAAAABJRU5ErkJggg==", "name": {"common": "Barbados", "cym": "Barbados", "deu": "Barbados", "fra": "Barbade", "hrv": "Barbados", "ita": "Barbados", "jpn": "バルバドス", "nld": "Barbados", "por": "Barbados", "rus": "Барбадос", "spa": "Barbados", "svk": "Barbados", "fin": "Barbados", "zho": "巴巴多斯", "isr": "ברבדוס"}}, "BY": {"currency": "BYR", "callingCode": "375", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAXdQTFRFoR0DoBoATJUDS5IA8tXV2YKC7MHB9+Tk35WV5amp3IqKsgEBsgAA5aur3Y+P7sjI2YOD2oaG2YCAswIC1XJy8dHR4Zyczlxc7sbG6LKyxkJC2oWF67293pKS6LGx46Ghzl1d7L+/0mtr4JiY1XV13pOT24iI3IuL3I2Nz19fyU1NzVtbx0VF2Hx80mpqwzg45aio4Jqa5q6u021t6LOzvCAg0mxs8M3NwDAwy1JS2oSExDs7xDw8wzc3xkFBx0REvicnswMDwTMzvyws13p6ux4eyUtL8MzM0GRk67u75q2t1XR035aW+evr1G9v7cLC+OjozltbsQAA2H5+5KWlylBQ6rq60Who6ri46bW14p6e2oOD4JaWswEBswAAyU5OwlVLwCUnnnI8zU9SsMCEQqAAQaAA4ZKVqahtQp8A1GdqmXM4Q6EC3oqNpZpfQ6AB3oeKr7uAQp4A5Z+hpJhd6q+ynH1C7sfH3IOG56apssWKQqAB////fb2DowAAAAR0Uk5T/v7+/rpKSvwAAAABYktHRHzRtiBfAAAACXBIWXMAAABIAAAASABGyWs+AAABAUlEQVQ4y2NgYWVj5+Dk4uYhABh4Wfn4BQSFhAkqFBEVE5eQlCKsUFpGVkhOXoGwQkUlZVEVLiFC6ngYuFTV1Pk0NAl7RktNW0dXT5+w1QaChkZcHMaErTYxNTO3sLQirNDaxtbO3sHGkbAbnUScXVzdCCt09/AU8vL2IexrE19DP/+AwCCCCoNDZNm45EMJB09YeERklEs0YV8TrZBoq4n2DCR4YmLjCACGeHCAJzAwEgCQKExMYmImACCJIjklNY0AgCSz9IxMQgCScLOycwgqBGeF3Lx8ggrBmaugsIigQnB2LS4hbDW4ACgtI6wQWKSUC1QUElYILqQqq6oJKQQA5hHNdDF/qqcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDUrMDI6MDAkzE+sAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjA1KzAyOjAwVZH3EAAAAABJRU5ErkJggg==", "name": {"common": "Belarus", "cym": "Belarws", "deu": "Weißrussland", "fra": "Biélorussie", "hrv": "Bjelorusija", "ita": "Bielorussia", "jpn": "ベラルーシ", "nld": "Wit-Rusland", "por": "Bielorússia", "rus": "Белоруссия", "spa": "Bielorrusia", "svk": "Bielorusko", "fin": "Valko-Venäjä", "zho": "白俄罗斯", "isr": "בלארוס"}}, "BE": {"currency": "EUR", "callingCode": "32", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAB5QTFRFpY0I+5QZAAAAAQEA/90M/9kM/9wL8xkw8xgw////QjDoUwAAAAJ0Uk5T/v6mqd4QAAAAAWJLR0QJ8dml7AAAAAlwSFlzAAAASAAAAEgARslrPgAAABxJREFUKM9jUAIDA9dQIAgT7wADhlHBUUFaCgIA0Ka53YIT3pMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MjYrMDI6MDC4wzlyAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjI2KzAyOjAwyZ6BzgAAAABJRU5ErkJggg==", "name": {"common": "Belgium", "cym": "Gwlad Belg", "deu": "Belgien", "fra": "Belgique", "hrv": "Belgija", "ita": "Belgio", "jpn": "ベルギー", "nld": "België", "por": "Bélgica", "rus": "Бельгия", "spa": "Bélgica", "svk": "Belgicko", "fin": "Belgia", "zho": "比利时", "isr": "בלגיה"}}, "BZ": {"currency": "BZD", "callingCode": "501", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABPBJREFUSMfFl2tsU2UYx5/nPafteltb2m0tdWOtZWwDB5ItzDE3AUMMsjEZCUzF8AFExRiMWQRciFGj0aAoQb8QLonGgFNCEEgwG7AFuo0MNrMM1o1d5LaVbl0vZ6c97Tnn9QPxay+LSd+8387znt95Lu/z/A+6ITOLZIgLrIyZYbM6KmcEjFWlB9M9QwgSBEopBUBESkGWadpgqGlLw5xBkGSIIURjwCgACUhxUCBkITAMSGngWUbkkhpRAEQgCPEwVWqUrmfjDVXFROkXxGiUt/Te5fqHIpFgiNUzlCKlgKm4QAtep4hJNkEEkCJkVaXi18+W16/nfOTPTdUuD/fH2gptxcpw3YsLg4J6dDQOLKWEkSHZCzGVkkYgFGQ+dqi59LcvHSf7Pg5EBYnqvMForqaoffjOg6C3e/Lk983qfe/m0hgClVNxGWH1+SQWBOgcHNlXRmy/zHEqh911pe0yiZAngWmrzjxzp2vpxi2u50rHJodzDWrWt31HSxsxqpOWGwMFTQkeE4I0LL1Sa9zZxIuCNhwLlQhW99VbHb0dzizjw5mpqfHBQNcNo87F5GgWmV2S7qbAuzwejlERmhCdKNQIIEuSSq95b6u5tffskHdkb/Vuz/UP69bXfv3RD7lWW5HN6VFqnPXb7l1of0F0DPiHxrz3396SDUoqJfM4IZggROnzxWqffD0qsuVmh7t1U41zqKGkz2Tir451c2Pj6kiM+oSxjo5TBw5YJ6I+fm6cv7yuvAD4GEMSpZpN9FEIsiiXl2QXWQsDc8Kyxav0jqWPPCc9gXyXQdt2tGv47u7GKb8yzptD75S/XFlT3yDcPlNTXNZ/MwIyC0jnCaaUAqNUqkNXPO0qzLOZnhGEqKFoPxAmONLNzfZYDUQMrFlQVLtnwy4Wea3eQCk9fuOEwfAGEAYSJjlhqBFBiufocurK6ixaCwAVRRrh/SBGDK6SHov1eKyedY5rVKNzXDwWEwAg35S/q3qnLdsGUgxxvqF+Cp+YmrswcJGVTWER+MeXVDJCYGarvJzOalbxuY0Fl/YMXKxf2DutVporc/vu3+6bDA2MrAVGSeftsUwBGOjzcI0rmrxhb99EV97iDeeHBx1f7O78rvH65MghoptsPbz/x+Y3T+9ltYqu8W6K8rrFr/q8CwBikLCPJLzHFICloWDWosInORZJFtGizwOiW2J1bCiu3mxTr9bzr+lh2YqyjRVrzfYCz9TwbPQR57cf+z0qKxI7nLi4ABiWDc/OuN2ulvftB899lW+yv7Ss9rKytFrkyxSyWq2CRdUXH4Ym84wVMHFtpLNh5ZrWM4Vx7g5jUEkJh1WSzkUpYBYz6OGyNNDStC4aY460H7HYlph0qkHftBHiPXHpMU7d/ef2rf4zLZs/6Ows+PbEIDGokrbM5EOCUkCVePhEwO1eIkgzRn3WenxyrfObqoLssxPnl2qFe73HtttwW23jp8cmWo6Ogo6VafLBnMJ0okCRMCp865O/Pv9JW5m3g9WZChfa7GY9F3soEX9z3f6eEXL856JTp1mRiSNhIAVBgEzVuRTQ/wkBTlJolFVl5lInLnfag5Hg8AP/g8f6rr+nwwE/a1ClLgTmJX0iEkgIkggMC0gA46BmgU1T+lRNd6YBfpoegoiAgFSmAEApyHz6Yq8/3RP/02K5TAl6kiFBn7F/p38BdMZJjC4jldUAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTcrMDI6MDB/+V4bAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjE3KzAyOjAwDqTmpwAAAABJRU5ErkJggg==", "name": {"common": "Belize", "cym": "Belize", "deu": "Belize", "fra": "Belize", "hrv": "Belize", "ita": "Belize", "jpn": "ベリーズ", "nld": "Belize", "por": "Belize", "rus": "Белиз", "spa": "Belice", "svk": "Belize", "fin": "Belize", "zho": "伯利兹", "isr": "בליז"}}, "BJ": {"currency": "XOF", "callingCode": "229", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAFRQTFRFMZQAL5MAUZ4A7dAA/9YAUZ8A7dEA/9gA/9cAL5QAUZsA6bwC/MAC+8ACL5UATYUC0j8N4zYO4jcOTIIDzioP3x8Q3iAQTIICzysP3yAQ3iEQ////MA4+ywAAAAFiS0dEGwJg1KQAAAAJcEhZcwAAAEgAAABIAEbJaz4AAABDSURBVDjLY2BAAYxMzCzYAcOowhGrkJWNnQMrQFPIycXNw4sVoCnk4xcQFMIK0BUKi4iKYQXoCsUlJKWwglGFI0chAFSNLGheco2sAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjEwKzAyOjAwul5glQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToxMCswMjowMMsD2CkAAAAASUVORK5CYII=", "name": {"common": "Benin", "cym": "Benin", "deu": "Benin", "fra": "Bénin", "hrv": "Benin", "ita": "Benin", "jpn": "ベナン", "nld": "Benin", "por": "Benin", "rus": "Бенин", "spa": "Benín", "svk": "Benin", "fin": "Benin", "zho": "贝宁", "isr": "בנין"}}, "BM": {"currency": "BMD", "callingCode": "1441", "flag": "data:image/png;base64,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", "name": {"common": "Bermuda", "cym": "Bermiwda", "deu": "Bermuda", "fra": "Bermudes", "hrv": "Bermudi", "ita": "Bermuda", "jpn": "バミューダ", "nld": "Bermuda", "por": "Bermudas", "rus": "Бермудские Острова", "spa": "Bermudas", "svk": "Bermudy", "fin": "Bermuda", "zho": "百慕大", "isr": "ברמודה"}}, "BT": {"currency": "BTN", "callingCode": "975", "flag": "data:image/png;base64,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", "name": {"common": "Bhutan", "cym": "Bhwtan", "deu": "Bhutan", "fra": "Bhoutan", "hrv": "Butan", "ita": "Bhutan", "jpn": "ブータン", "nld": "Bhutan", "por": "Butão", "rus": "Бутан", "spa": "Bután", "svk": "Bhután", "fin": "Bhutan", "zho": "不丹", "isr": "בהוטן"}}, "BO": {"currency": "BOB", "callingCode": "591", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABtQTFRF/7YAP7MA/wAA/wUA//8A9vsAAJkAAJoA////tCvPzgAAAAJ0Uk5T/v6mqd4QAAAAAWJLR0QIht6VegAAAAlwSFlzAAAASAAAAEgARslrPgAAACtJREFUKM9jUMICGIaKoDEWwIAVuGABg1EwFAtgEMQCGNKwAIZyLGAQCgIAwqugtTXBw2MAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NTArMDI6MDDR1gVRAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjUwKzAyOjAwoIu97QAAAABJRU5ErkJggg==", "name": {"common": "Bolivia", "cym": "Bolifia", "deu": "Bolivien", "fra": "Bolivie", "hrv": "Bolivija", "ita": "Bolivia", "jpn": "ボリビア多民族国", "nld": "Bolivia", "por": "Bolívia", "rus": "Боливия", "spa": "Bolivia", "svk": "Bolívija", "fin": "Bolivia", "zho": "玻利维亚", "isr": "בוליביה"}}, "BA": {"currency": "BAM", "callingCode": "387", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAsFQTFRFTTpX5LcX/99R/MMA/8sC/8wAMTO3lpG8//fH/uRU/NEg/soA/80C/8wBLSyp1dTs////yL22+tQG/9IA/soD/8wCAwOXlpXSrKzcY2O+TThU8cAA/9kv/cwR/8kADg6fFxegAACSHR2kBAevYleH7+K2/+Rg/NEi/ssDAACYAQGYBASbAACWEA+clpjb8O70+N9J/9AA/coEAACZAQGZBQWbAACRV1e33NzvsrLhZ1BW8sQC/9UK/s8c/8oAMzOuCAiYKCinDxGyOi953L5a/+yN+8oF/s0OAgKZAgKXAACTVljF4OH4///8+/Gf/tMR/ckAAgKaAwOWysnm8/X/oo9+6LsA/9kA/swP/80EAACXHR2lQUGzIB+jOTu+Jh6BzqIP/+qC/M4e/80BAACVAQGXGh2xsa/V//7w/PXG/d9P/cgAb2/D39bK6b8J/9oA/ssF/8wG/80DXFy9QUGwX2DFODCGxZgK/9cy/ttV/sUAAQGaBQWYAACUBQSUAACkcm60/fC+/PXT/OJ0/c4UAwOaJyer6On59dpT/9QAJCOkhIPKd3jLeW6SwJUU/80A/tpR/8oHCwueBgaXAgKTAgOlNDKl7dNu//bO/OB0/dQz/8sAAQGVCguho6Xj+/TC/9gU/8wDAwObfXzEo6Tht62yzqIQ/c8h/9Ii/8gAHh6lAACPERKpFhSiyqs+/++D++WP/tIj/84LAgKWAACaZ2nO/v7+/uZ1/coCJiWiy8vr0tLo8Mor/s8A/9EgJyepGhmfEhOiKSipp4g6/90V++ql/9AU/88ONTW25+Lb+/HG/NMxBgaalpXT9fj/+ueE/9EA/swD/80M/80JBwecT0+2EhKhVFOzoYJA/88A/d5o/9lHAQGWDA2mzL+o///k/PXb/eN7BQWaLi+0+fr//Pjj/9gVSEiyVFS6YWG9za1OtDXQrwAAAAFiS0dEEJWyDSwAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAG/SURBVDjLY2BgZGJmYSUMGNjYOTi5uHl4CSrk4xcQFBIWERUlpFBMXEJSSlpGVo6AmQzyCopKyiqqauoarHiVMmhqaevo6gnoGxga8eJTyWBsYmxqZm5haWVtY2vHi0+hsbGJpr2Do5Ozi6ubO27rQQqNjXU8PL28fXz9/ANEefEqDDQJNA4KDgkNC4+IxKsQaH1UdExsXHxCYhJ3Ml6FxsZaKalmaekZmVnZWKMJodA4MNDULEcgNy+/oLAIr0KgUp3iktKy8orKqiL8Co2ra2rr6hsam5pbWEXxKjT2aG1NaWsX6OgsZCkSxacQZH1Xd09vX/+EiXb4FRprTpo8Zeq06TNmzpqNHPqYCo01W7XnzJ0nMH8B90IWHnwKgWCR2eIlS5cZLl+xkhe/QmPtVavXrF23fsPGTTDv41BoHKW9ecvWbQLbd/Cy8OJVaGLcWrtz1+49ezX2zZ6NTyFQqc7+AwcPHT5y9NhxUCrFrRBovZaYzomTAqdOz17Ii1ehiUn1mdVnz52/cPHSZbwKQWF65eq16zdu3rq9koBCY+M5d6Lu3rv/4CFBhYHVj7weP3n6jKBCoNLa5y9evgIAadzd4Wn8vlkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzIrMDI6MDCAJh3/AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjMyKzAyOjAw8XulQwAAAABJRU5ErkJggg==", "name": {"common": "Bosnia and Herzegovina", "cym": "Bosnia a Hercegovina", "deu": "Bosnien und Herzegowina", "fra": "Bosnie-Herzégovine", "hrv": "Bosna i Hercegovina", "ita": "Bosnia ed Erzegovina", "jpn": "ボスニア・ヘルツェゴビナ", "nld": "Bosnië en Herzegovina", "por": "Bósnia e Herzegovina", "rus": "Босния и Герцеговина", "spa": "Bosnia y Herzegovina", "svk": "Bosna a Hercegovina", "fin": "Bosnia ja Hertsegovina", "zho": "波斯尼亚和黑塞哥维那", "isr": "בוסניה והרצגובינה"}}, "BW": {"currency": "BWP", "callingCode": "267", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABhQTFRFAMv/Acv/AMr/Dsv7////o6CfAAAABAQEhk2unAAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAA5SURBVCjPY2AYfEAQC2BQwgIYjLEABhcsgCEUC2BIwwIYyrEA7Cop1I7VSVgdj9WbWAMEa9ANPgAAKWdaUey6wNYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDErMDI6MDDQg2u/AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjAxKzAyOjAwod7TAwAAAABJRU5ErkJggg==", "name": {"common": "Botswana", "deu": "Botswana", "fra": "Botswana", "hrv": "Bocvana", "ita": "Botswana", "jpn": "ボツワナ", "nld": "Botswana", "por": "Botswana", "rus": "Ботсвана", "spa": "Botswana", "svk": "Botswana", "fin": "Botswana", "zho": "博茨瓦纳", "isr": "בוצוואנה"}}, "BV": {"currency": "NOK", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAATtQTFRF1ygo2Csr1Boa7rCx////S3CzACyRBz2arsHf2UFC1iIi1ykp2C4u1R4e7rGy2URF1iUl2C0t0xkZ1Bwc0QoK7KqrSm+yBj2aACyQrcDf1TM10hIS8quq8qyr8aWk99/fS3K0rcDe8LOz8ain+P//9Pj7SXG1AC2Rp7rb9Pv9GUmeGkugG06iCD6aADeWATmXEkaeHE6iGUmfADSVADiXADWWADaWADeXD0OcD0KcD0OdD0SdBDuZCkCbEESdDkOd6/P66/P76O729/j8RW6zBj2ZAC6SoLXY+fr96O326/D3//Ty/Pv7S3O2rb/e+/z+2DY22TY22TY32DIz9NXXS3G0rL3c4XV32kBA3UxM3ElK1iQk1iAg9NLTrb3c3VdY0xQU1iMj1yoq9NPU3l5f2Cws3l1e1R0d1ysrhhJo8AAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAD3SURBVDjL7dLJUsJAFIXhG7h2kAhGUIKKOCENqEElEkUBZ3DCAXACAXHA938CU0UnRVV3sXSj3/qvszoAjORy4wiRiQdHvQoM8R/+UigxYz4/C8fVCYkHASY4OYUhooWncWbWFeBBhJmLzuOCtbiIS8sRAYg5VjBONZrAZEwEUo7VtXU9rW9sbqVEIDNANwxjOyMG2QGmFepZMdix7eb29k3DzBeKORE4sB0eHZ/Q07NS+fxCBC4dV3hNZFrBGxSB2767+2oN60SjD/j49PzCg0afqjZfrVCmLWx33po8UJiu+s7CD99nl+ecQ2nY4Vfve9jN/lT4AwuzdojtfpIUAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjU4KzAyOjAw4jlLNgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo1OCswMjowMJNk84oAAAAASUVORK5CYII=", "name": {"common": "Bouvet Island", "deu": "Bouvetinsel", "fra": "Île Bouvet", "hrv": "Otok Bouvet", "ita": "Isola Bouvet", "jpn": "ブーベ島", "nld": "Bouveteiland", "por": "Ilha Bouvet", "rus": "Остров Буве", "spa": "Isla Bouvet", "svk": "Bouvetov ostrov", "fin": "Bouvet'nsaari", "zho": "布维岛", "isr": "איי בובה"}}, "BR": {"currency": "BRL", "callingCode": "55", "flag": "data:image/png;base64,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", "name": {"common": "Brazil", "cym": "Brasil", "deu": "Brasilien", "fra": "Brésil", "hrv": "Brazil", "ita": "Brasile", "jpn": "ブラジル", "nld": "Brazilië", "por": "Brasil", "rus": "Бразилия", "spa": "Brasil", "svk": "Brazília", "fin": "Brasilia", "zho": "巴西", "isr": "ברזיל"}}, "IO": {"currency": "USD", "callingCode": "246", "flag": "data:image/png;base64,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", "name": {"common": "British Indian Ocean Territory", "cym": "Tiriogaeth Brydeinig Cefnfor India", "deu": "Britisches Territorium im Indischen Ozean", "fra": "Territoire britannique de l'océan Indien", "hrv": "Britanski Indijskooceanski teritorij", "ita": "Territorio britannico dell'oceano indiano", "jpn": "イギリス領インド洋地域", "nld": "Britse Gebieden in de Indische Oceaan", "por": "Território Britânico do Oceano Índico", "rus": "Британская территория в Индийском океане", "spa": "Territorio Británico del Océano Índico", "svk": "Britské indickooceánske územie", "fin": "Brittiläinen Intian valtameren alue", "zho": "英属印度洋领地", "isr": "הטריטוריה הבריטית באוקיינוס ההודי"}}, "VG": {"currency": "USD", "callingCode": "1284", "flag": "data:image/png;base64,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", "name": {"common": "British Virgin Islands", "deu": "Britische Jungferninseln", "fra": "Îles Vierges britanniques", "hrv": "Britanski Djevičanski Otoci", "ita": "Isole Vergini Britanniche", "jpn": "イギリス領ヴァージン諸島", "nld": "Britse Maagdeneilanden", "por": "Ilhas Virgens", "rus": "Британские Виргинские острова", "spa": "Islas Vírgenes del Reino Unido", "svk": "Panenské ostrovy", "fin": "Neitsytsaaret", "zho": "英属维尔京群岛", "isr": "איי הבתולה הבריטיים"}}, "BN": {"currency": "BND", "callingCode": "673", "flag": "data:image/png;base64,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", "name": {"common": "Brunei", "cym": "Brunei", "deu": "Brunei", "fra": "Brunei", "hrv": "Brunej", "ita": "Brunei", "jpn": "ブルネイ・ダルサラーム", "nld": "Brunei", "por": "Brunei", "rus": "Бруней", "spa": "Brunei", "svk": "Brunej", "fin": "Brunei", "zho": "文莱", "isr": "ברוניי"}}, "BG": {"currency": "BGN", "callingCode": "359", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAB5QTFRF////5vLgSqEfL5MAMZQAMJUARoYCyS8O3yAQ3iEQJE7QjgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAxSURBVCjPY2AYykAQC2BQwgIYjLEABhcsgH6CoVgAQxoWwFCOBTB0YAEMM7GAQSgIAPGQqgHN6gB8AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjA1KzAyOjAwJMxPrAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowNSswMjowMFWR9xAAAAAASUVORK5CYII=", "name": {"common": "Bulgaria", "cym": "Bwlgaria", "deu": "Bulgarien", "fra": "Bulgarie", "hrv": "Bugarska", "ita": "Bulgaria", "jpn": "ブルガリア", "nld": "Bulgarije", "por": "Bulgária", "rus": "Болгария", "spa": "Bulgaria", "svk": "Bulharsko", "fin": "Bulgaria", "zho": "保加利亚", "isr": "בולגריה"}}, "BF": {"currency": "XOF", "callingCode": "226", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAThQTFRF3gAA3wAAyxMASo0A3gAA4BMA618A3wYA9asA9aoA4ykA/N0A7GcA//MA3wgA9a0A//cA9awA3QAA5C4A/N8A//QA5C0A4h0A6EkA6EgA50UA8YsA6EcA4hwA3wAA4hQA8YAA/eAA//AA/u4A//EA8X8AyxIAyxEAyxUA1V0A6tIA/vUAS4wASooAVqIAq9EA9vAAS40ATYoANKIAM6EAOaIAer0A3eUAM6IANqAANaEAMZ8Ad7wA+vEAdrsAN58AM6AAnMsAm8oANKEAQqYA1OIA+/IA0+EAMqAAarYA5+kAi8QA5+oA9u8AabYAo84A//UA/PIAwtoAXbEAw9sAos4ARqgA2uUA7uwAkscAQKYAQaYAk8cA2eUARagAb7gAxNsAZLQAZbQAbrgAVa4AR6gAVK4A////1Ljd9gAAAAR0Uk5T+/v8/rlpGqcAAAABYktHRGdb0+mzAAAACXBIWXMAAABIAAAASABGyWs+AAABIklEQVQ4y2NgYCEOMAxRhaysRCpkYyNOITsHJztRCrm4ubmIUsjDy8tDjEI+fgEBQT58CoUgQFhEVFREDMrBplBcQkISCKSkeYFAWkoGyJaQkEUoZJSDAnkFRSVlIFABKVQBsZQUVeVhsnIMTGpQoK6hqaUtygsHotpamhrqMFk1BmYdONDV0zeAqxQ10NfThUsZGjEYIwETUzNzmEJzM1MThIyFJYMVCrC2sYWos7WzRpGwR1Po4Ai1W9TJAZ9CZxdXUV5RNzcg4e7ijEehh6cBr6iXt7ePKK+vnwc+E/0DAoOCHRyCQwIDQvGY6BwWHhEZBXScQ3RMRGycM06FHvEJic5gaWfnpIRkD9xWp4TBJT1S0/C50Rk7GzMccYKBVAgAeBqY4SGl3bgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NTcrMDI6MDAUcTvfAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjU3KzAyOjAwZSyDYwAAAABJRU5ErkJggg==", "name": {"common": "Burkina Faso", "cym": "Burkina Faso", "deu": "Burkina Faso", "fra": "Burkina Faso", "hrv": "Burkina Faso", "ita": "Burkina Faso", "jpn": "ブルキナファソ", "nld": "Burkina Faso", "por": "Burkina Faso", "rus": "Буркина-Фасо", "spa": "Burkina Faso", "svk": "Burkina Faso", "fin": "Burkina Faso", "zho": "布基纳法索", "isr": "בורקינה פאסו"}}, "BI": {"currency": "BIF", "callingCode": "257", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAeZQTFRF/3R0/wcH/wAA/wQE/wIC/////8/P/z09/wEB/wMD/z4+//39/6am/xsb/v////z9//z8//Hx/3Nz/wUF/f/9/P78/9TU/0ND9/73//7+//v7/6mp/x0dMNgwuvK6/v/++/77//Pz/3d3/wYG/wwM/09P/4yM/qinAM0AWOBY4/rj/9fX/0dH/2ho/+bm/f7+A9ADA88DAMwADdENiumK+f75/6Gh/vz7//7/5/zp5vzpAM8AAs4CAMsAKtcqzu7Dt4dLzu/DAc8BBNAEAc0BVN9U4PngurR+9AAAAs8CDNAMhuiG+P74ssmF1SkJ1CkIs8mGJdYlxPTE/f78xN+yxOCzB9EHAMoAh+eH/v7+/v39/f7//v79hueGqe+p/v3///3/+/762e/UtdWZ/P///f37/f77u9mj0OnG+v75wuaxw0gX4RcErKdf9P/9//3++///rrZv3B4FyjsRu9ygx+Cz3RAAt4BJ+P////v9//v8t5Ze5wUAwc+eh+mH/f791/XSuqFnt2wxxeCt9v73+v/7yOa2t3U6uZVa0vLLVd9V4fnh/vv78f703Pvh4Pvk7f3wu/K7/f/+/vr6/vr7i+mL/6ioWeBZ/9bW/0ZG/2dn/+Xl//Ly/3Z2/wsL/4uL/0JC/87O4JBTPgAAAAFiS0dEBfhv6ccAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAJ6SURBVDjLjVQHV9pQGI3Bq6FojAhqRAQHGjfgShuxFidKh9pqFVQ67N7bDruX2qGtuNc/7ZcE7GkLPbzzTt57+W7ezbvf/R7DpLGGdPa/Ld3ApjEMMjJZzsglh6nBQxlgTEBWNms0JsNRJDsLMDF8joBccx5rTMifbmTzzLkQcnjGYoVgQn4Byxk4nclgpK7P6VVBPkwCrBamkB5iEWAr1vjjf8sZNdZiG1Ak0maFjL0EDqeDPiotK1dDFa7KqqpKV4U6Ly8rJToKo8TOSFJ1Dawaf20dy9U3oJHnG9FQz7F1tRqrFTXVksS4Pd6m5haNX0SrDWKbfPiI3CbC1gpRY21pbvJ63IyiuNt9HTo/SSUI/NHOzmOqFjDprB2+dreiEFDxd3VLPb20KcHQhr7+/j4aaE7b9fZI3V1+RQcqSsDjHRgMwgHaBsdPnDxFA2gZHBzwegIaRAcSf8A3NAwLhUdw+gw/QhMLhod8AXcMEAcq/tGxs3BifAJyKCRjIkyLybFRv/I3UHFLU3DK06EIwmFEQtOyE1OSW0kMPIfzFy7OAJf4y1dwNTGQqCeBa9dv3LwVuX3n7r37D5CQWj8MHj5iHz+Zfco/Y5+/gPzvYVR55oJ4iVev37x9B/n9h4+fPiOM4Nwf8qiCzy+ogi9GvnxV1XTg23d5URV8Yf634GoKl2IpXP4xS2cBZmZ/LsdSuBRLoWqKlQNTRDEuFFETxvnogSlWNFNI0mrcZmvrLLexCRHUNzc4dn0tbrNVspldYyWzbG1rxt1x7Uaju64dzbjbW1QuGr899VKIFddesuLaixcXL6RSrgKf+gWA/RSulMx9MObULinzL4qXnyvu2DYjAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjI1KzAyOjAwiSsj7wAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDoyNSswMjowMPh2m1MAAAAASUVORK5CYII=", "name": {"common": "Burundi", "cym": "Bwrwndi", "deu": "Burundi", "fra": "Burundi", "hrv": "Burundi", "ita": "Burundi", "jpn": "ブルンジ", "nld": "Burundi", "por": "Burundi", "rus": "Бурунди", "spa": "Burundi", "svk": "Burundi", "fin": "Burundi", "zho": "布隆迪", "isr": "בורונדי"}}, "KH": {"currency": "KHR", "callingCode": "855", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAhNQTFRFGwBzHgBxHgByDAB7DAF7DAF6gQA8gQA9gQE+gAA7gQk/gQlA9QAA9AEA9QMD9AAA+3Nu+3Br4wAI5R4i4wEI4wML4QAA73t97nh74wAH5gAG5gAF5wgN8nx95gEH5gAD6BEU96mp5gEI5QAA6B0f96+w6R4g5gQK5gAC71td/N3d71tc6Bwe96+v6Bse5QAB5QED72Fi+9TU8GRm5gQG5QEB5AAA8G5u+cXF+9nZ5QIC71pc+9nY7lVX7UVJ+cDA/OTk8GNm6icr6iQp96ut+9jY+9fY6SQp6ics8GFl+9ra+cHB+s/P7URH5gII9qCh/N7e+b/A/OHh/evr/e7u+s7O+Li4+9XV/evq+b+//N/f8XV2//r6+cjI/e3t/OPj/ebm/OLi/enp/efn/e/v/vj48XR2+cPD/ODg+sjI+tHR+srK+9LS+9PT+cbG+cfH/ejo8XV35gYL4wAA8XBw+Ly8+sfH+snJ+szM+b29+Lu78W9w5gYK5gAH5gMI5QAC8nZ4+cLC+9fX+9vb+9bW8Gpt5gQJ5QIE8n1//vT0/Nzc+tDQ/err/efo/Nzd/vPz4wEJ4gAD73h8/f//+/3+/P//+vHx+NPT/P7+++zt+uvr+vHy4gAC/V1Z/2hj/2Ne/2Zh/l9b/VRQ/2Rg/2Vg/2Vh/l1Y/lxYgAA8fwAsfwAqfwArfwAufwAtDAN+DQR/////x3+KlQAAAAFiS0dEsENkrsQAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAHFSURBVDjLY2AYToAREzAxYRFkYMYELKxYBBnYEICdgx1EcnJxc7Kzw7gwwMCDBHhBBB+/gCA/H5wLAwxCSEBYBEiIiolLiIkCGZJgLgwwSMGBtIysjLSUnLyCoqKCvByUCwcIhUrKKqpqyurqGppaWtoa6urKOrp6+kpYFEobGBoZm5iamVtYWlqYm1kZWNvYKsthKpRTtrO0d3B0cnaxdHWzdHH38PTy9vFFqIQr9FP2Dwg0CgoOCQ0zCg/zCYkIcouM8lf2w1CobhYdExsXH5+QmBQQlQykHFJiU9PM1LEoTEtK18rIzMzKzsnNy8nMz8jwLihUxlAoV1RcEl6a7VOWXW5fEVpZnp3nk12VW21WI4eqsLauviG3vDG7ybK5KbelKaep2bKpPD0zt1W5rRZZoZx0e0dnXFe3Vk9CQm9BVFRyQUJCj1Z3X1x/R7s0xExIFE6YOGnylKnTpk+bOmPq1KkzZwGJGUDu7KlTJk+aMwEShdA4nztv/oKFixYuWLxkydJly5csWbIYzJ0/by4sUbADwYoV7CtXrVy9cs3K1SCwdi2YAnGBwiBZIIAl3HXrgQBMIABEDJZwGYkEA51lqQoAC02qK305p84AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NTkrMDI6MDBETkCCAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjU5KzAyOjAwNRP4PgAAAABJRU5ErkJggg==", "name": {"common": "Cambodia", "cym": "Cambodia", "deu": "Kambodscha", "fra": "Cambodge", "hrv": "Kambodža", "ita": "Cambogia", "jpn": "カンボジア", "nld": "Cambodja", "por": "Camboja", "rus": "Камбоджа", "spa": "Camboya", "svk": "Kambodža", "fin": "Kambodža", "zho": "柬埔寨", "isr": "קמבודיה"}}, "CM": {"currency": "XAF", "callingCode": "237", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAQVQTFRFAJgRBJURiiwFxQAAwgAAwQAA1EUA/usB//ABxQwAyBcA0TwA3m4A33QA8r0Bxg8AwwcA7asB/egB0TsA00UABJQRiy4FyhAAxxYAyBYA0DYA+NcB//QB44QByBgAyRwAyh4AyR0A11IA/uoBizAF3FcA8rwB9s4B/+8B//EB/OQB99AB+NMB5YkB2l4A/ekBiisFxgMA1UoA88IB//IB++QB4XsBxQ4A00QAyRsA5o0B/uwB9s0BwgMA0kAA/ecB66MBwwUA4XwB/u4B+dcByyUA8b0B4XkBzCYA5YgB22YA0DgA8LQB1EcAwwYA2mAA5o8BxAkAzCcAyiAAwgEAxQ0A////kwE6UwAAAAFiS0dEVgoN6YkAAAAJcEhZcwAAAEgAAABIAEbJaz4AAACxSURBVDjLY2BAAoxMzCxwwMrGzoEEGEYVkqqQk4s4hazcPEQq5OXjJ0Yhq4CgkDArkBbBrVBUTFxCUlJKWkZWTl5BUUkZt0IVVTV1dWkNTS1tHR1dPX3cVhsYGhnLmHBwaMqYmplb4HUjq6WVNQeHiY2RLSHPsNrZa5rIODgS9DWrk4yzi6a0KyshheZuWu4enkJehBSyevv42rL6+QcEEjIxKBgY2CwhrqGDOz0Of4UAVDUip2lhZrwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MjYrMDI6MDC4wzlyAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjI2KzAyOjAwyZ6BzgAAAABJRU5ErkJggg==", "name": {"common": "Cameroon", "cym": "Camerŵn", "deu": "Kamerun", "fra": "Cameroun", "hrv": "Kamerun", "ita": "Camerun", "jpn": "カメルーン", "nld": "Kameroen", "por": "Camarões", "rus": "Камерун", "spa": "Camerún", "svk": "Kamerun", "fin": "Kamerun", "zho": "喀麦隆", "isr": "קמרון"}}, "CA": {"currency": "CAD", "callingCode": "1", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAATtQTFRF5Zysvwowvwkv/////vr7//7+/vz85Jmo9drg//3+9NTbxiNF34OX/v3978HL6rC82nOJvgYsyC1O67G95Jen/PP17LbBwxg7wRE2vwkwvwsxwBA11Ft09djexR9CvwguvgYt4Iea+u3w/ff47bnE89LZ4Y2f2WyD9+HmzDpZvgcuwA4067K+67O/1Fx189HY7bvF9tvh12Z9wxg8wRM4vwwx23eM0Etnvgctwxs+4Y6gxylKxB1A8MfQ56KwwRI3wA0zwhU5yjNT+urt2nCGvgguxSNF/fj5whY60VBr56OxwA4z2W2E+ert7LfCyCxM4Iqc/PX345KjvgUswA0yvQMqzT9c/PL045Sl6q262GmA5p+u34OW34aZ+/Dz//393oGV9tzh/vz923aL9NXc/vv88s/W8MTN+uvuEtZ6YwAAAAF0Uk5T/YPqVscAAAABYktHRAMRDEzyAAAACXBIWXMAAABIAAAASABGyWs+AAAA60lEQVQ4y2NghAImBmYsgIEJJs8wRBWysBKnkI2dgxiFnMxc3DzMvKwEFfLxCwgKCYuIElQoJi4hKSUtw0ZAoaiorJw8I6OCIrMSHoXKKqpq6hqaWoyM2jq6evoGhrgUshkZm5iamVswMllaWTPayNnitNpOkolJ2x7IcXBkYlRwcsalUNQFLgxkuLrhNNFdzcOVEc5n8uTAaTWbl5wWjOvt44sneFj9/CEyTI4BgfgDPCjYNUTbWzI0LJxAzPCbRURGRUfHxMYRUBjPzMybkAh0BCvhZJaUnEJcekxVTyNOYXx6xuDPhXRSCABQTCDtFbfICAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNToxNyswMjowMH/5XhsAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MTcrMDI6MDAOpOanAAAAAElFTkSuQmCC", "name": {"common": "Canada", "cym": "Canada", "deu": "Kanada", "fra": "Canada", "hrv": "Kanada", "ita": "Canada", "jpn": "カナダ", "nld": "Canada", "por": "Canadá", "rus": "Канада", "spa": "Canadá", "svk": "Kanada", "fin": "Kanada", "zho": "加拿大", "isr": "קנדה"}}, "CV": {"currency": "CVE", "callingCode": "238", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAihQTFRFCBhzCRlzDh52CBhzCBhzBxdzBhZ0EyBuDhxxBhd0ChlyBxhzJS1mg3M+amBJGCRsCRhzFCFuVVFSKjFkLzVi1K4bsJQqFyNsChpyNDlgOz1dDBtxUE1U4LgVloE1FiJtR0ZYQUJaCxpyKDBlt5onwqIiMTZhBxd0BhZyBBV0KjFji3g5XFZNBhdyBBVzBRVzDx1ugnE9inc5FSFsBRZzJzWEIS+BITCCIzGBJjJ7JjSBIzGAJzR+IjCBJDKAKTZ+KTV+JTOCJjJ83d/s3N7r3N7s3N3j3dKq4Mp33NjM3N7q297r3NnR38t83dGh3Nzg///////4/+h0/9gk//bB//jN/9ks/+Vl//709cXA9cO+9beC9bJs9b6k9cO/9b+p9bNt9bZ99cK830Iz3jsr3jss3jwv3jwu4lJE4Us94Uw94Uw+4Uw/4k834Us+5Fgy4lE5+uLg+uHf+uLf+uDZ+ten+8xk+tm2+uHd+ter+8g8+t3M///9/+2M/9ce//Ou//rd/99I/+l9///8n6XIm6LGm6LHnKLIn5uXnJN4np6lmqHJm6HHmqHIm5+6m5yrm6HEDx93BxdyBxh1HCZpFCFtBhdzDBtwIitnBRZ0ESB4LjRjnocyiHc7IClpBRV0ER9vFSFtbmNHrpMrQEJbCRlyJS1nwqEip40uISpoemxCYVpMFiNthHM91a8aQEFbEiBuLTNj17AZs5YpGSVsKTBlGyZrSEdXc/d3NwAAAAR0Uk5T+fv89h27Wh4AAAABYktHRFDjbky8AAAACXBIWXMAAABIAAAASABGyWs+AAABdUlEQVQ4y2NgYCEOMIwqJFEhK5EK2dg5OAkqZOVi4ebh5eNnJaBQQFBImEtEVExcQlJKGq+JMrJygvIKikrKKqpq6mgKGTUQQFNLW0dDV09fw8DQyNhEAwUwmJrBgLmFpZW1jS2IaWfv4OhkbW6GBBicXaDA1c3dw9MLyvF28fH183d1QQCGADgIDAoOQfBCw8IjApAAQ2QUHETHxMbB2HHxCYlJUUiAITkFDlLTUpBAeioyL4UhIzMLCrJzcnOy4CAvvyAzCwkwFBZBQXFJaVl5BYxXVFlVVg2XAwIkz9TU1tUjeA2NTc0onmlpbYOA1vaOzq7uHiivt6+/f0IbEmCYOAkOWCabTJlqMmka66Rp02fMVFdnnYQEGGZxIyWe2XPmzpu/QJpt4aLFS5aixTUTEkd92fIVgitXrV6zdO269WxoCpmRONwbZm9UUtu0eQvL1o3bWPEoZGHh5GQVl9kuzcLJhp520RSC84I6CxaARSF2AADWZMJ/dOCgpQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo0NSswMjowME9EKmgAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NDUrMDI6MDA+GZLUAAAAAElFTkSuQmCC", "name": {"common": "Cape Verde", "cym": "Cape Verde", "deu": "Kap Verde", "fra": "Îles du Cap-Vert", "hrv": "Zelenortska Republika", "ita": "Capo Verde", "jpn": "カーボベルデ", "nld": "Kaapverdië", "por": "Cabo Verde", "rus": "Кабо-Верде", "spa": "Cabo Verde", "svk": "Kapverdy", "fin": "Kap Verde", "zho": "佛得角", "isr": "כף ורדה"}}, "KY": {"currency": "KYD", "callingCode": "1345", "flag": "data:image/png;base64,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", "name": {"common": "Cayman Islands", "cym": "Ynysoedd_Cayman", "deu": "Kaimaninseln", "fra": "Îles Caïmans", "hrv": "Kajmanski otoci", "ita": "Isole Cayman", "jpn": "ケイマン諸島", "nld": "Caymaneilanden", "por": "Ilhas Caimão", "rus": "Каймановы острова", "spa": "Islas Caimán", "svk": "Kajmanie ostrovy", "fin": "Caymansaaret", "zho": "开曼群岛", "isr": "איי קיימן"}}, "CF": {"currency": "XAF", "callingCode": "236", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAMNQTFRFj4//jY3/jo7/kYz62/Hb3OzWGaQZHqAZhM4AhsoAAAD/Dw/wBQD6sgBN/gAB+gAFV1eotQBK/wAABQX6LS3SPDzDtbVKPj7BMDDPAwP8W1uk2dkm/f0C2tolWlqldnaJ//8AeXmGAQH+jY1ymppll5dokJBvAgL9Bwf4MjLLBwf3Bgb4MDDNCAj33ylK//////r6/0pK9UU/9UY/vS8HvC8HAJkAAJoABZYAtSwABZcAtS0ABZUA2zsA2zwA//oA/0oAWGHrOAAAAAp0Uk5T/v7+/v7+/v7+/iz0QYUAAAABYktHRC8j1CARAAAACXBIWXMAAABIAAAASABGyWs+AAAAwElEQVQ4y+XPRw+CQBCG4cHOYgFRFCv2XrBhl///q0RHYmY8wM1E38NevifZXRBeiiL80plsziubSQsaPA5VfUMt/0z7hHqhaBilsh4IzUq1Vqs3zEAoRNOyWiGuFu1Ot9cfhIDD0XgynQVCLykSkeBVdI5wHgXWgra0EdpLNvwWjNHiK4TrOBsgQUtuEG6TbIAdydkfEB72Dl3AoR1PCE9HNnwT0ifvzv5nzmyAFE2+ILzKbACLdnMRujc2/Ce8AwJCj6qxb9w/AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjE0KzAyOjAwThFEhgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToxNCswMjowMD9M/DoAAAAASUVORK5CYII=", "name": {"common": "Central African Republic", "cym": "Gweriniaeth Canolbarth Affrica", "deu": "Zentralafrikanische Republik", "fra": "République centrafricaine", "hrv": "Srednjoafrička Republika", "ita": "Repubblica Centrafricana", "jpn": "中央アフリカ共和国", "nld": "Centraal-Afrikaanse Republiek", "por": "República Centro-Africana", "rus": "Центральноафриканская Республика", "spa": "República Centroafricana", "svk": "Stredoafrická republika", "fin": "Keski-Afrikan tasavalta", "zho": "中非共和国", "isr": "הרפובליקה של מרכז אפריקה"}}, "TD": {"currency": "XAF", "callingCode": "235", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABJQTFRFAABnnJwo//8A/5wA/wAA////tPKynwAAAAFiS0dEBfhv6ccAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAaSURBVCjPY2AAA0YlMDBxAQOGUcFRQVoKAgCIN03vQD/YJgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1NiswMjowMLIGMGsAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTYrMDI6MDDDW4jXAAAAAElFTkSuQmCC", "name": {"common": "Chad", "cym": "Tsiad", "deu": "Tschad", "fra": "Tchad", "hrv": "Čad", "ita": "Ciad", "jpn": "チャド", "nld": "Tsjaad", "por": "Chade", "rus": "Чад", "spa": "Chad", "svk": "Čad", "fin": "Tšad", "zho": "乍得", "isr": "צ׳אד"}}, "CL": {"currency": "CLF", "callingCode": "56", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAMlQTFRFVVCpVE+oaWWz6ejz/v7+/f39W1asXFesl5PKW1esU06oUEunb2u2bmq2iobDmZbLm5jMjIjEVlGqhYHBg3/AUk2nfHi8fHm9Uk2oiobEjYrFmpfMWVSrnJnNVlGpX1quVE+pXlmuVFCqUk+qaGW16er1/v///f//Z0uVZkqUeV2e6dHW/OTf++Pe2ywU3S4W7D4d70Ef7icA7SUA7ScA2tnsubfb+fn73t3u/f39/Pz9/f3+3dzt8fH3////8vH47ez1ycjj7Oz15wJcQwAAADV0Uk5T/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v70ZoPgAAAAAWJLR0Q+SWQA4wAAAAlwSFlzAAAASAAAAEgARslrPgAAAJNJREFUOMtjYEAGjEzMLKzYAQPNFLIRqZCdg5MYhVzcPKa83FwEFXLy8ZuZmwkIshNSKCRsYWllbSMiRNBqUTFbO3txUcJulJB0cHSSkiDC19JSMpKycoQVCskrKDAqChFhogIU41OohAyUVVTV1LEDBg1koKmlraOLHTDooQB9A0McgMEIFRjjAgwmRIJRhSNHIQCeJpKdP7BkTgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNToxNSswMjowMOhmTzIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MTUrMDI6MDCZO/eOAAAAAElFTkSuQmCC", "name": {"common": "Chile", "cym": "Chile", "deu": "Chile", "fra": "Chili", "hrv": "Čile", "ita": "Cile", "jpn": "チリ", "nld": "Chili", "por": "Chile", "rus": "Чили", "spa": "Chile", "svk": "Čile", "fin": "Chile", "zho": "智利", "isr": "צ׳ילה"}}, "CN": {"currency": "CNY", "callingCode": "86", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAVZQTFRF/zMA/zQA/zIA/y4A/y8A/zEA/zUA/zcA/0YA/08A/2kA/yIA/zYA/zkA/yoA/4UA//wA/8AA/0oA/50A/ykA/4cA//8A/8sA/1gA/ywA/04A//gA/1MA/ysA/2MA/3EA/x4A/yUA/4wA/y0A/8YA/80A/zAA/zwA//cA/1cA/0AA/3wA/+YA/+IA/9wA/98A/+gA/+sA/+AA/+cA/+0A/4IA/3YA//IA//4A/z0A/zsA/0kA/yQA/0EA/8UA//0A/8kA/0MA/9IA/7AA/7wA/ycA/3QA//UA//sA/7UA//oA/8cA/z8A/4MA/5EA/+4A/9sA/zgA/28A/4kA/3kA//AA/4QA/7oA/+kA/9cA/9EA/5gA/0sA/1YA/ygA/0QA/8gA/0gA/7MA/70A/zoA/30A/68A/4YA/1AA/20A/yAA/5MA/9QA/2UA/4AA/64A/2EA////WzUIRwAAAAFiS0dEca8HXOIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGtSURBVDjLjZJpV9NAFIbnTjIzEUMbJVxIYYRYTQDZESglWBbjUitQNmVVFhVw4/9/IvVgM7Q5J7nzZc7MM+97lyGkGUCUoJrOOEkOqu6F8aDjoZkMdip7nstbjx5TSNLjXSLW5HY39vSyBBCcQl+/bGZFReHJwGCStXDz+NQV8YFTNNuKYQ3F3DN87hBKqXdn0W4LPmNcKwyhNawJ4CMxAaDioL0YHRufmETEqbHpmZez2v9L8BjTVWFnbh5LEYelEs7PyaaCZyyUF1WSB0uv0IrACi6vBM0SQF9YXjUcVVLo7hpaFQtfh5pQcnrz9l3eu1eUCN9jNVofpNIgwmsf1331IMpmaQMrm1XsKxRVAVn3pcoRM9zCoW17Zxf3AnUaQFuGaLqfPvcH3DX2D6R6Ay1dB692WLcFEU5x/Sj2Bq7xlvFQU3oNS07teL6gh8cnWit59xQ4jW2+DHzdOfWSvuT9fGj9DM8vaCoYVfjt+4/QTOUa/Q58k2YACTNZCsfhH5CaHs155SyOtHw5e3WdoQouf+Kv33p6X7j9Z+Nvhv5FpFG+kVlAorNiG3cLqPQrR7LSmnIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDcrMDI6MDCzU16FAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjA3KzAyOjAwwg7mOQAAAABJRU5ErkJggg==", "name": {"common": "China", "cym": "Tsieina", "deu": "China", "fra": "Chine", "hrv": "Kina", "ita": "Cina", "jpn": "中国", "nld": "China", "por": "China", "rus": "Китай", "spa": "China", "svk": "Čína", "fin": "Kiina", "isr": "סין"}}, "CX": {"currency": "AUD", "callingCode": "61", "flag": "data:image/png;base64,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", "name": {"common": "Christmas Island", "cym": "Ynys y Nadolig", "deu": "Weihnachtsinsel", "fra": "Île Christmas", "hrv": "Božićni otok", "ita": "Isola di Natale", "jpn": "クリスマス島", "nld": "Christmaseiland", "por": "Ilha do Natal", "rus": "Остров Рождества", "spa": "Isla de Navidad", "svk": "Vianočnú ostrov", "fin": "Joulusaari", "zho": "圣诞岛", "isr": "האי כריסטמס"}}, "CC": {"currency": "AUD", "callingCode": "61", "flag": "data:image/png;base64,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", "name": {"common": "Cocos (Keeling) Islands", "cym": "Ynysoedd Cocos", "deu": "Kokosinseln", "fra": "Îles Cocos", "hrv": "Kokosovi Otoci", "ita": "Isole Cocos e Keeling", "jpn": "ココス(キーリング)諸島", "nld": "Cocoseilanden", "por": "Ilhas Cocos (Keeling)", "rus": "Кокосовые острова", "spa": "Islas Cocos o Islas Keeling", "svk": "Kokosové ostrovy", "fin": "Kookossaaret", "zho": "科科斯", "isr": "איי קוקוס (קילינג)"}}, "CO": {"currency": "COP", "callingCode": "57", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAC1QTFRF/+gA/ucB/+wA7toKESKEABCQARWNABSOBBSMABWXbQpP6gAH1gAS2gAQ////+isIvAAAAAFiS0dEDm+9ME8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAA/SURBVCjP5cgxAQAQAADBr6CCCiqooIIKKqigggoqqKCLAr+a3HjwsSCIgiTIgiKogiY8u2AIpmAJtuCIF3kBsZjHOdeRgOYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzMrMDI6MDAmURZLAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjMzKzAyOjAwVwyu9wAAAABJRU5ErkJggg==", "name": {"common": "Colombia", "cym": "Colombia", "deu": "Kolumbien", "fra": "Colombie", "hrv": "Kolumbija", "ita": "Colombia", "jpn": "コロンビア", "nld": "Colombia", "por": "Colômbia", "rus": "Колумбия", "spa": "Colombia", "svk": "Kolumbia", "fin": "Kolumbia", "zho": "哥伦比亚", "isr": "קולומביה"}}, "KM": {"currency": "KMF", "callingCode": "269", "flag": "data:image/png;base64,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", "name": {"common": "Comoros", "cym": "Comoros", "deu": "Union der Komoren", "fra": "Comores", "hrv": "Komori", "ita": "Comore", "jpn": "コモロ", "nld": "Comoren", "por": "Comores", "rus": "Коморы", "spa": "Comoras", "svk": "Komory", "fin": "Komorit", "zho": "科摩罗", "isr": "קומורו"}}, "CK": {"currency": "NZD", "callingCode": "682", "flag": "data:image/png;base64,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", "name": {"common": "Cook Islands", "cym": "Ynysoedd Cook", "deu": "Cookinseln", "fra": "Îles Cook", "hrv": "Cookovo Otočje", "ita": "Isole Cook", "jpn": "クック諸島", "nld": "Cookeilanden", "por": "Ilhas Cook", "rus": "Острова Кука", "spa": "Islas Cook", "svk": "Cookove ostrovy", "fin": "Cookinsaaret", "zho": "库克群岛", "isr": "איי קוק"}}, "CR": {"currency": "CRC", "callingCode": "506", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAC1QTFRFAAC0AwO1AACwSkrK/////Pz+//39+dTU2QAA2QMD1wAA5EpK//z8/f3+1NTyOWQfkwAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAABKSURBVCjPY2AgGghiAQxKWACDMRbA4IIFMIRiAQxpWAB27eVYAEMHFjDAgjOxAIZVWADDbiwAu9/PYAEMd7EA7NrfYQHEpwUKAQB++dslvrN5NQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNTowOCswMjowMEUbLmwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MDgrMDI6MDA0RpbQAAAAAElFTkSuQmCC", "name": {"common": "Costa Rica", "cym": "Costa Rica", "deu": "Costa Rica", "fra": "Costa Rica", "hrv": "Kostarika", "ita": "Costa Rica", "jpn": "コスタリカ", "nld": "Costa Rica", "por": "Costa Rica", "rus": "Коста-Рика", "spa": "Costa Rica", "svk": "Kostarika", "fin": "Costa Rica", "zho": "哥斯达黎加", "isr": "קוסטה ריקה"}}, "HR": {"currency": "HRK", "callingCode": "385", "flag": "data:image/png;base64,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", "name": {"common": "Croatia", "cym": "Croatia", "deu": "Kroatien", "fra": "Croatie", "hrv": "Hrvatska", "ita": "Croazia", "jpn": "クロアチア", "nld": "Kroatië", "por": "Croácia", "rus": "Хорватия", "spa": "Croacia", "svk": "Chorvátsko", "fin": "Kroatia", "zho": "克罗地亚", "isr": "קרואטיה"}}, "CU": {"currency": "CUC", "callingCode": "53", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAoJQTFRFsBQ9LEHEAFf/AlHzA0/sAVDwAFDw9wAA6QMIfyVuD0zkAFb/AlDvA0/t6gEC7gEC/wAA2gcWXDCRAlL2AFT8Ak/u7QAA7AAB6QED8gAB/gAAvBAxOD22AFX/AVL3A07sAVDvAVHw6wEC+AAA9gAAmRxTHEfVAU7xAEnsAE3w6gED6wED/QAA5wQLcid6E1nsEmz/ElrvEVnuEVzx8AEC/AAA4xgg5p6q7/z/7vz/7e/77vH87vP+8AEB8QAA9kRB/9LQ//////79//78///+7QIC7AEC7AMD6wAA82do/enq/vz8/vv8/v7/7QEB7QMD7AAA7hMT95KS//r6//z8//398To67QQE8Cws+rq6//7+///87QUF7QoK+r6+7xsb7AEB9E9P+Njb3/v/6vP98vL77AIC8DIy8k5O7AgIkTJxGlnoAWH/E1vwFFvu8kRE9oOD9GNj+aqq+r299Gdn94iI81VV+gAA0QYZUS+XAFH5AFH6AEzu7xkZ+rm5+8nJ7yMj6gID8gEB+wAAxw4oOD65AFP3A1Du9Xd3//j495GR9AAB+QAAvBEyM0C+AFP282Fh/vX19oWF7QYGygggRzOiAFL7AVH5+8HB/NHR8Ccn+bCw/eHh7QID6goMhjZ9FFvtAmH/FFvv7xoa9oGB7QcH9XR08DAw9Vtb99/i3vv/6/P88fL78TY2+8XF+J+f7AID7AcH9HR1/fDw8AAB91BN/9zZ7QAB8gEC4iEq6Ku37///7vr+7vL94AYRZSuHEF3yE2v+EVrv8gEDjCBgFEreAU3wsRQ8LUHCAlL10AoeTzWfAFP6AFT64wQNcSl8CU/rA0/uAk/tohlMIUXPAlHy6vewcQAAAAFiS0dEQYnebE4AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAHgSURBVDjLY2BgZGJmYWUjCBjYOTi5uHl4CSvk4xcQFBIWYRElpFBMXEJSSlpGVk5eQREfYBATE1OSUFZRVeNW19DEA0AKxcS0tHV09fQNDI2McQKIQjExCRNTM3MLSytrGxwAplBMXMvWzt7B0cnZxRErgCt0FXNz93D39HL09vHFBuAK/YDY3z8gMCjYMSQUn8KwcJDqcNeAiEjHqGgX7Fa7xrjGxsWLxbgCmf4JAYlJySmpWD0TkOaR7pjh4R4gBrE/Mys7JzcPI3j8xPILCosci0tKy8AuFfN3La+orKquQQ9wP7HaOsfQqBDH+gaIQjG/xqbmlta2dvQoFHP16HDsdOwKSICFQGN3T29fP6bCmIAJjhMdJwVMhqjz5y+fMnXa9BrMuHYNmDFz1uw5/pDAnBswb/6ChYsWYca1n9jiJUsDApYtD3AFBk9AwIqVq1avwR7X4e5+/q5pMcAA9wtYuw53gAPtA1rqDwyXgNr1UQSiUExsg7vHxk2bCSYKca0tdlu3AZOZI95ktl1iR/POXbv3WO3Fn3C13KT27T9w0OgQ/qygpFV++MhR7mMEMpe4RLfp8RPcJ1lY8WdXPhOBU6fPnGXhIVQA9Jw7f0H24iXCRcrlK0xXWQiXUWwAJhP4Hi78yG8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MjkrMDI6MDBOi0mbAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjI5KzAyOjAwP9bxJwAAAABJRU5ErkJggg==", "name": {"common": "Cuba", "cym": "Ciwba", "deu": "Kuba", "fra": "Cuba", "hrv": "Kuba", "ita": "Cuba", "jpn": "キューバ", "nld": "Cuba", "por": "Cuba", "rus": "Куба", "spa": "Cuba", "svk": "Kuba", "fin": "Kuuba", "zho": "古巴", "isr": "קובה"}}, "CW": {"currency": "ANG", "callingCode": "5999", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAALFQTFRFACt/ASx/ACx/ASp+ACl+DzSEACp/BjGDABx2YXmuCzSEip7E6OzzASyAAi2AACN6KEuS9/n7ASp/ASt/ACJ6PV2dVHGpCC6BBS2AASF5BSyAAid8ABhzUmymAyd8Ay2AJkuSBzGDrLrVACd9Z4Gz8PL3+fr8ABZyY32w////BC6BAB53mqvMpbTSACp+BjCCQ2ShAiJ5Ay1+ACGFQFxj//AQ8+MX//YMfYlKAB2HBC59XgHWQwAAAAFiS0dEKcq3hSQAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAC2SURBVDjLzdFJEoJADEDRMAkINiAgomIDyqDMznr/g0l5gWTBwle9/JWqpAFGggAkoiSJpFBWFJmQzVRN1zV1hnWCPDdM05jLC6xklu04tsWWLhZ6/mrlWywIRGzoOgw3210U7fZI6HLG4yRNk5hhKy0OxyzPs+MB3YgX5elUFhw95rmqm6auzmjYdv0w9F2LhgAe5x4hG8/5e1O4EMGVCG5EcCeCBxE8ieBFBG8i+BBN82t/4gtP7VYkdbck8AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1OCswMjowMOI5SzYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTgrMDI6MDCTZPOKAAAAAElFTkSuQmCC", "name": {"common": "Curaçao", "deu": "Curaçao", "fra": "Curaçao", "nld": "Curaçao", "por": "ilha da Curação", "rus": "Кюрасао", "spa": "Curazao", "svk": "CuraÇao", "fin": "Curaçao", "zho": "库拉索", "isr": "קוראסאו"}}, "CY": {"currency": "EUR", "callingCode": "357", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAltQTFRF//////78///9//77///+/+yc//K6//Cz/+2i//74//zy/+2h/95V/+yd/95U/91Q//nh//Cx/8oA/9tD//TI//zv//PC//K9//XL/+mL/9IV/8gA/9k5//G0//O+/+Fh/++s/+eH/8sA/9AH/8wA/9Yo/+Bc/+Jq/+Z9/8wB/80A/8wD/9xF//PD//O9/9Qr/+eA/84G/9EO/9EP/84A/88D/84C/88E/9Uv//jX/+V3/84B/9o8/9k4/88H/+Rz///8/9Yq/80B/84D/9IS//bO/9xU/8kA/88F/9Qh/+Ff/9AT//PB/fK+/tg0/ssA/ssB/88C/80G//fV/v///vTI/u+s/fK9/v34/vXH/+BX/dcv/tEY/+Z//+Nv/+R0/9o+/9MZ/uV5/f///v76/f79yenG5PThoNupvOXA++2m/M4F/tMe//zu/P32/P36v+a/otqi6PbmzOrJ/f7+yOnIR7ZHmtaYPa88oNqg/v/+/vri/v79ptylPrA+mtaaSLdIxejF+v36ZMJkitGKveW9PK88UblQvOW/6/jy/f/+/v7+7vjuwufBVLtUPK48ueO5jNKM+/37+Pz4seCxQbRBgM2A4/Tje8p7Oa04LqsswObAwufCMa0xOKw4eMh44PPghc+FQrRCrN6s9/z3uOO4csdyYMBghs+Gj9OPdch1dsh2i9GLr9+viNCIZsJmbsZus+Gz2PDYlNWUYsFieMp4VbxVmNaYfMt8ZcJlQLNAjtKO1u/W+/774fPhhM6El9aX2/Hb0e3RzevN2O/YktSSf8x/3PHc/P78Kb+E8wAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAHXSURBVDjLY2AYLoCRCasoMwsSj4WRkRmrMgagKiZGmBpGZrAIKxua+YzMQIKdg5OBhZmZmRGshoubh5ePXwDFJBagdkYGQSEhYRFmEF9UTFxCUkpaSEaWgQlZHa84g5y8AoOikrKKiqqaugaflIqmlpC2ji4DCxPC1YwMetL6BoZCmkbGJiamZipKSkJKWmbmQhaWQDlGJIuZGaxUDIXMzM20tEyAwMwcCKyttWzkGJgZ0fzKaatiB1RibW0CAUC1SvYMjA4YgcLsqGVmggDWTkLO1i6uDBhhyMjg5u5h6gw0zxRoq6mplqeXt5KQDwMjA4aRvn7+ASpOgdZAjyhpqgQFh4SGhXNijZOIyKjoGBUzmdi4+ITEpOSUkBBsqlIZ0tIzMhmysnNygaaH5OUzFBQWFTOUYFFYWlZeUZlaVR3ByJhSk8pSW1ff0AgURgdVDE3NLa1t7R2dXQwM3T29ff0TJjZPAgpjgslTpk6bPmPmrNkMDHPmzpu/YOGixUuw+qWKYemy5SumrFzFwLB6zdp16zdsxGoeEDQxbNo8dcvWbZu39+3YuWv3HqAADrC3qmnf/gMHDx0+cvTY8aaqvQw4QeoJBPtEKgM+0H3iRCoQAAmGoQcAg35zKD2gjyEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTUrMDI6MDDoZk8yAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjE1KzAyOjAwmTv3jgAAAABJRU5ErkJggg==", "name": {"common": "Cyprus", "cym": "Cyprus", "deu": "Zypern", "fra": "Chypre", "hrv": "Cipar", "ita": "Cipro", "jpn": "キプロス", "nld": "Cyprus", "por": "Chipre", "rus": "Кипр", "spa": "Chipre", "svk": "Cyprus", "fin": "Kypros", "zho": "塞浦路斯", "isr": "קפריסין"}}, "CZ": {"currency": "CZK", "callingCode": "420", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAARFQTFRFT0+c5OTw////+/v9/f3+AABnGBh7rKzR/Pz9AgJxAgJrAABlVlag6urzAABvAgJwBARxFhZ5q6vQAwNyAgJsAABmV1eh6+vzFxd6WFii6+v0ra3RWVmi7Oz0GBh6rq7SWlqjr6/SW1uj7e31/v39//7+GRl7sLDS/v7+AgFqUF2r7ubs/+/u/O3t/e7uAwBtAQJ0AAN2UAA43AsO7BMS6RER6hERAgBuAwBuAAB3HQBiqQAe/AMA6wEB5gAA6AAAAQByAAB0XABC4QAG9gAA5AAC5wEB6AEBqAAe+QAA6wAB5AAB5wABHABiHABjpwAeWwBC+AAA4AAGpwAfAgBtAQBzAAB1WgBDHgBiVQBG3AAIE5FEqAAAAAFiS0dEAmYLfGQAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEBSURBVDjLjc5nUwIxFIXhGKqUFVCQqlIUFRWlW3ClY6WoiP7/H+LeDAwku8nlfH7nmUPIFrXZKT7icLqo3b1BuO3x+vwboETbCXiDIRwlmqbt7oUj+xgKIaBR7CkLGRqLK9FFyNCECl2GDE2m5OgqBPTgUIquhYA6jmQoFwLqTFujfMjQTJbacsfixBDQkzw9RUVAz8LnhYvLK34WYfH6plSuVGv8TGG9cXt3/9B81IWJYfGp1e50e/2BOGLihs8vr7p5xIJ709Whwb1LOC5UcWuhwX2MpNwqNLixgluGjJsouEUI3FTJsRC4T4SD8Ot79oNyEM7mQ5yD8PdvgnPG/gE0npRdY58b+QAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo0OSswMjowMIjkQBwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NDkrMDI6MDD5ufigAAAAAElFTkSuQmCC", "name": {"common": "Czech Republic", "cym": "Y Weriniaeth Tsiec", "deu": "Tschechische Republik", "fra": "République tchèque", "hrv": "Češka", "ita": "Repubblica Ceca", "jpn": "チェコ", "nld": "Tsjechië", "por": "República Checa", "rus": "Чехия", "spa": "República Checa", "svk": "Česko", "fin": "Tšekki", "zho": "捷克", "isr": "צ׳כיה"}}, "CD": {"currency": "CDF", "callingCode": "243", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAe9QTFRFAH//AX/+AoD9BIH8AHr/AXn+a6ab8eQg8rcY2EQfAn/+BX/6A4H8An79AHf/M4/Pws5M/9gM5XYazhUhzAchAH3/GYfoYKClAHv/A4D8AHj/E4Htlrp0/uQQ9Z4O0SkjygAizg4hzhIhBID7SJe70Mk6AYD+AoD+AXr+YqOj6N8m/8EJ2kwfyQQkzQkhzxQhzhEhzhAhAHz/BH/7hq2B/90HJovbAX//NI/OxdBJ/9kI53QYyhElywQizxMhHonjEYXvD4TxyMZC/98AdKiSEIXwGIjoFYfrAH7/lrp1/eMR9Z4Q0CkkyQEjzg8hzAkhygckcaeV89Ub6tEk5tAo99YX+NYX8tUc4s4s9tUZ0sk7L5DT6N8nyQAj0Swj9JsRUZu07dQd/toP9tUa99YY9tYZ/9wHu8FPzxIh6HUY/9kJJIrd488q+dcW79Mf/9oNnLdtAn39/8IJ6d8mYKKmHIfl6NAm/9oM/NoNnbVtBYH70Cok9Z4Plbp1A3/9AHn/YaGi/+QAs75XYqGk89YZ7NUbEIPx53UY/9oIxM9JtsBRm7ZuJ4zb280wWZ+qYqOkSZm6GYXnRZi/A4D9AX7+AXz+BIH7BYD7AXr9YaKl6eAm85sRygYk/eQQzhYh5nYawc5M2EUf8OMhaqac////DOZeaAAAAAFiS0dEpFm+erkAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGhSURBVDjLY2CAAEYGHICJmYWVjZ2DE8rl4sahjoeXj19AUEhYBGKcqJi4BBMWZZKSUtIysnLyCopKIGWMyiwqqqJqXIxo9qtLSmhoamnr6OrpGxgC+UaixnwmpmYsvKKiyMrMgbZaWFpZ29jaKRkqAU2UtHdwdHRydmF0dXP3QLXV08vbx9fPztBA3z+AgdEjMCg4JDQsPCIyKlqUCdnWGKitdn6xcfFAJ7IkJCYlp6SmpYtJqGGzNcPWJjPLEmSJVHZObl5+AUshEzZb9XR1iopLNEC+9igtK68wrWSpwmarb3WNV620VB1QRrm+obGpuaW1zYMRi63tHZ0WfLw85qBIkujqLuTr6e2rl8Ria0w/q4SkOjhaPFwnsPBWsUycZCSJHMJwWyUl4WEmMZmRgWnKVOZCrLYipxAQMW06Vlu50KJfnZCtOEIY01YcIUyJrYxE2spQR5StQDBj5iyCtoKBZRZhW8FgdlwsQVvBYI4/YVvBQIkIW8GAGFshJioqyMvNlcFrKxiIzJsvuICfj5eAMgaGhRyLFrOyMDMRUgcAjPCYa9vcYiMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MjIrMDI6MDCjTnZfAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjIyKzAyOjAw0hPO4wAAAABJRU5ErkJggg==", "name": {"common": "DR Congo", "cym": "Gweriniaeth Ddemocrataidd Congo", "deu": "Kongo (Dem. Rep.)", "fra": "Congo (Rép. dém.)", "hrv": "Kongo, Demokratska Republika", "ita": "Congo (Rep. Dem.)", "jpn": "コンゴ民主共和国", "nld": "Congo (DRC)", "por": "República Democrática do Congo", "rus": "Демократическая Республика Конго", "spa": "Congo (Rep. Dem.)", "svk": "Kongo", "fin": "Kongon demokraattinen tasavalta", "zho": "民主刚果", "isr": "קונגו - קינשאסה"}}, "DK": {"currency": "DKK", "callingCode": "45", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAADBQTFRF+xsi+xgf/EpQ//Dx//39+xsi+xgf/EpQ//Lz////+xUc/EhO//Ly/HB0//X1//7+C2BCZQAAAAV0Uk5TtbW2t7cPsqOPAAAAAWJLR0QJ8dml7AAAAAlwSFlzAAAASAAAAEgARslrPgAAAFBJREFUKM9jCAWBsIqZ7amhMBDAMPwE00Aga8/M08vSYCCRoRwEqu/NfLu9HAaKGDpAoOf9zH8nOmCgmWEmJpiCXRCrdqwWYXXSIAw6GggCAFkM9x0CLLn8AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjMxKzAyOjAwsc4HYgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozMSswMjowMMCTv94AAAAASUVORK5CYII=", "name": {"common": "Denmark", "cym": "Denmarc", "deu": "Dänemark", "fra": "Danemark", "hrv": "Danska", "ita": "Danimarca", "jpn": "デンマーク", "nld": "Denemarken", "por": "Dinamarca", "rus": "Дания", "spa": "Dinamarca", "svk": "Dánsko", "fin": "Tanska", "zho": "丹麦", "isr": "דנמרק"}}, "DJ": {"currency": "DJF", "callingCode": "253", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAWVQTFRF0N/+dKL/WpH/aJv/Z5r/Zpn////+8fX+mLr/XZP/Y5f/aZv//v3+/v7+zNz+cKD/XJL/aJr///7+/f3+8vX+mbv/XJP/y9z+cJ//y9v+b5//ytv+//39l7r/ydv+//v7/+rq//r68PX+l7n//////2lp//Hxydr+bp7/Zpr+ZZn+Zpn+//Ly/6Wl/5CQ/w0N/4OD/6Cg8PT+lrn+WZL8ZJf/aZn/aJj//52d/w4O/wAA/wcH/46O/v78///81tz/bKHzXZztYJ3uX5zu//z8/0ZG/zAw+/36oO+WEc0WBcgRCMkSB8kR/93d/3R0//X1/39//8rK/f78+/773/jfRdpFAMcAAcwBAM0A//j4//f3/v/+meqYDc0NAMkABM0EAcwCAMwB4PjfRtpFAMgAAssCA80DAMwARtpGDc4N+/37muqZDs4N4PjgR9pGDs4O4fjgR9pH/f38m+ub3vjeoOygE88TFak2pQAAAAFiS0dEJLQG+ZkAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFISURBVDjLjdH3P8NAHMbxUNKiIVKzQlq+9t7UqBFqj1JFqVVqb/5+NxJpIr3z/Px5vS+XE4SCQk9RMX+C6PWVlJb9I/RLYnmFXMlFBcUfEKuqa7iooChKQBJr63goDjHq9XFQEhK0PshEjZCiMgM1Q4I2MFAzVCnamBfNCQnaFJTdH4qGmhYKaxRtbnFHaQhqaxuYX9reIXs6u7odQ6EKPb19/QODIVDp9YeGR0bHxh3DogoTk5Gp6d/rz8xG5+YXFu0jR+uwFFlWCIgWk2BldW19wz4qbm5t7+yCjrP4HuwnDpKHzhERjsKgH5PbxFJwcpo+O79wzviPuq4Z3OVV5vom+3fWE1pcMssOMXfrztlCzN3l4XLC+D2Ls0LEPTzm58wQcU/PLM4IMffC5EiIuNc3DofD9xR8cDkcwmcizeVw+PWd4XNoP36z6HdatyBvAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjI3KzAyOjAwHrQyxgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDoyNyswMjowMG/pinoAAAAASUVORK5CYII=", "name": {"common": "Djibouti", "cym": "Djibouti", "deu": "Dschibuti", "fra": "Djibouti", "hrv": "Džibuti", "ita": "Gibuti", "jpn": "ジブチ", "nld": "Djibouti", "por": "Djibouti", "rus": "Джибути", "spa": "Djibouti", "svk": "Džibutsko", "fin": "Dijibouti", "zho": "吉布提", "isr": "ג׳יבוטי"}}, "DM": {"currency": "XCD", "callingCode": "1767", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABFJJREFUSMftVltsFVUUXfucM4/7aC1tsSnyhhSEhvIKYEuNBGNADRIiaIIBMdEEIij8EI1fRmJMRH8A/VGCRiMhgiAkiMFHQgMKNgZEhKA8mwLtvbS9vffOnZlzth+K+MPcqeCX7uyPSWZmrey99ouq3kQcU4RuH+vGYOMkIIElz2HHFwDQ/mP7lKYp2UJm+taGrkLWksTMcQBFLNp/wf4nHogRARRL0DtCzID584kBJgCA1gOFUQOLkYEEUAEpyAINgpFaA2ApBxp9XOI/QIsJ4BfZs9d5pUOtajWKuPbFZfmZs3new54lfAnbQFM8wNqNMT4CfEJgsLk9seiYkylStcL2VTkRYtFmu48KjuseaFAvz873uJzWMDG4Ca+XZ2WG4+KT3emRlfzlvOKT76etnMjUGYYYfFV4g/DBqt7HdjreJbXgidxVZjI3tI9I9ZZJ5YgN/DRGfJh46JR9YkZpxK+KAyKmusu+UH4gK6nAU9qsMC8mXlMftyWPr84n/Bv1F6Xd6cj3DDjAeZlZl74uuKpXgtkIYar6j1a9FGarZxXXK+1SQJAoWZzwUL0hj9YAuTIdI2AjwlkBlcjtsT9v9vY9ndeSAxchoJJib9OBTUNbvrPXk+jxk4I1/9TsbV3R37nfggQ7iEZW85dFqQtGKLGhW00bzj1dggEyJCzf6xiiUmvaG9eezM5rLVRCaCKq6BJj07KvXT2/hPrBEojoL4rICAEMtkCHxlbca0QpQFDBYGFx4Yozbm/d0TcmTZ7a+/NnX9fkbE2A9MgJkE1x69n+jlALUITSAjCRziAjtcnW6WsjAzYgsCHLDa/ZJmy+PPerYTheZZKaoNEzOLwyOiRmAX1jwt3S1aG2Q5HFxXCdoetf+Hbi4XONavlbKQAh2XV+5+jc27vuTqKI39Jmcp/wAzrRHJwe5y3fNmrHO7uChEva4NZNpVqaW6KIdQipcnMeGL/98JhzaWYQgVh3uRXV9Nq5MYIYJQqJ7cDiCe3u0GOe2zht5pw5MAYiqqxFmUxDAEg+vnSEVzvsJNgCMwRwIRFgCO9sKi7O0MSMKBHY4epO0XDWvWvxCgBsOBqZWraVmSDCcF9SPvvpxaUngk1r8/cfdCccddgBfMDCD7Xh5E51aYK/b2Fx5ebUkUHi1WeGJwNiMChqepEdY2RqQlLQeweTNcN0xSU56rzlCfTWagpR321pKTrqg+7xnjxlLZ9buOgYR5efXCptl1/JguELs+LB/LZvUlPP2H0aaYU9Cwt2iRa8a0oo1J+xLgTuyvm5bIprQmFkWUhQzCvTAroCrGzEFqjsHrvriLpeYxTzPaX69LRZpUcfabq8OtPbnWbS8dZy3H3MBDBSBWBWWH1fuOYp2r1fVJHe9/1H9dNn+IWMu9XYGkZFjqt/QAwABENAHjDwmfuh+xnasgCQ1jfPoHg28JtL/PXT3449GUPV2yW+aTzQKO8U8W3Zf4/4dx1H/6K/wN0DAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjIyKzAyOjAwo052XwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToyMiswMjowMNITzuMAAAAASUVORK5CYII=", "name": {"common": "Dominica", "cym": "Dominica", "deu": "Dominica", "fra": "Dominique", "hrv": "Dominika", "ita": "Dominica", "jpn": "ドミニカ国", "nld": "Dominica", "por": "Dominica", "rus": "Доминика", "spa": "Dominica", "svk": "Dominika", "fin": "Dominica", "zho": "多米尼加", "isr": "דומיניקה"}}, "DO": {"currency": "DOP", "callingCode": "1809", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAR1QTFRFzNbr////+NjV2Tgp1igX1ikY1ikY1igX2Tcn99bT0NntEj+jADCcADGcADGcADCcED6iET6iztjs////99fU2Tgo1igX1ikYAC6aAC2aETuhztfs2DUl1SUT1SYUSWy4SGy4VXa93OPx+/z95+vx6+/0/f//+uPh5HFm4mZa+fr9+/z+3uXyorXQrbzSxMDKysvV7vH3//v7/vr6ydnUfJqunqi53JiJy6yp3Ojqxt2xlaaaw6y0wKevrracz+fDwtyvvpdp1oyLmYejdqN9x+O53eLKwqh72qCHv6GWpqh26O3d8LOt8LKs8biz+NfU+dnU8+Pa9uTb+dLN7ODmq7zfpLXcpLbc1ioZ99bT0NntE0CjATKc2TcnEj+jqOAFTAAAAA50Uk5T9/Xz7e3t/Pz8/v79/Pw+trfuAAAAAWJLR0QB/wIt3gAAAAlwSFlzAAAASAAAAEgARslrPgAAALFJREFUOMvt0bUWwlAQRdGHewKEENzd3d3d3f7/MyBQzUtDwaLKLmedqS4iIFKLBC9CkVgCITzU6akXA21kID78d2iCzBbrO7TZHRByQi63x+vzB4KhMAZFMNFYPJFMpTNZDKJwuXyhWCpzztywUq3VG80vwla70+31vwgHw9F4MuWGM2i+WK7Wm+1ufzhC6AQx9Jn9v1yJG4SwAYz3T/ggscn48EehFJLJFWyoVKk10BOl7H44XRu8uAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1NCswMjowMCWZIUIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTQrMDI6MDBUxJn+AAAAAElFTkSuQmCC", "name": {"common": "Dominican Republic", "cym": "Gweriniaeth_Dominica", "deu": "Dominikanische Republik", "fra": "République dominicaine", "hrv": "Dominikanska Republika", "ita": "Repubblica Dominicana", "jpn": "ドミニカ共和国", "nld": "Dominicaanse Republiek", "por": "República Dominicana", "rus": "Доминиканская Республика", "spa": "República Dominicana", "svk": "Dominikánska republika", "fin": "Dominikaaninen tasavalta", "zho": "多明尼加", "isr": "הרפובליקה הדומיניקנית"}}, "EC": {"currency": "USD", "callingCode": "593", "flag": "data:image/png;base64,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", "name": {"common": "Ecuador", "cym": "Ecwador", "deu": "Ecuador", "fra": "Équateur", "hrv": "Ekvador", "ita": "Ecuador", "jpn": "エクアドル", "nld": "Ecuador", "por": "Equador", "rus": "Эквадор", "spa": "Ecuador", "svk": "Ekvádor", "fin": "Ecuador", "zho": "厄瓜多尔", "isr": "אקוודור"}}, "EG": {"currency": "EGP", "callingCode": "20", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAQ5QTFRF/zQ0/zQz/jIu/jMw//Hx//Dv+ujQ+ufP/u7o1tbW1dXS09HG09DD0tDB0tDA0tDC09DC09HE1dTRFRUVFRUWFRUU/wAA///////+/Prq+vfg/Pvv///97N2F7uKV8eak8+qx8eaj7+Oa692C/Prs+/fj4Mk83cQo4cpA9e2/3sYt3cMn38g4+vbe+vbc4Mg438Yx5dFZ9u/G4s1I3cQq38c0+fTX+PTW38Yy4sxH7eCN7N6I7d+L4Mg53sUu9/LO59Rj8+mw7d+M4ctE8eal8+mv5dJa/Pnq+fXa9/DK69x/4co+7uGS+PPV+fXZ+/jl/fvv/Pje+vXS+PDC9u23+PHD+/bY/fni/fvsAAAAZyyrpQAAABZ0Uk5T/v7+/v7+/v7+/v7+/v7+/v7+/v7+/sBIbWkAAAABYktHRBcL1piPAAAACXBIWXMAAABIAAAASABGyWs+AAAAo0lEQVQ4y2MQIxIwjCqkjkIMwMjEzIANsKADVjZ2DhZMwCCOBiTEJaWkxWXQhcXRFUrLyskrKCopqxBSqKqmrqGppa2jS0ihnr6BoZGxiakZIYXmFpZW1ja2dvaEFNo7ODo5u7i6GRFS6O7h6eXt4+vnT0hhQGBQcEhoWHgEhkJOVMDFzcPLxy8gKIQmzskgjAZEhEVFRYEkOmCIJBKMKqSOQgDjLcbGnZDVKQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNToxMiswMjowMC3BcbwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MTIrMDI6MDBcnMkAAAAAAElFTkSuQmCC", "name": {"common": "Egypt", "cym": "Yr Aifft", "deu": "Ägypten", "fra": "Égypte", "hrv": "Egipat", "ita": "Egitto", "jpn": "エジプト", "nld": "Egypte", "por": "Egito", "rus": "Египет", "spa": "Egipto", "svk": "Egypt", "fin": "Egypti", "zho": "埃及", "isr": "מצרים"}}, "SV": {"currency": "SVC", "callingCode": "503", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABCFJREFUWMPtl9trXFUUh799zpn7mWvGSS/TNEmNNCSVRFOsaMWoFaEPaisKPojV/8EXfVbwrYIgKIWCliK1hQrFikhRKCShJmnLtBJTe0vGmSSTmc7tzLns7UNLH8WTtFiwC/bbZu1vr7X3Wr8l9n/SUDzApv3XAA8BHwLeW3fizrp3ZtwbKNCwEcoBQIoQ6q7r9RWJdQAKQBJQVWJynghlJDoaDjVtB7pq45DA0jawnqgaao0XNKgTk/Noqo0UIWriMRySKAwkAeLyMknmEQzRFnkU+trOScWEP0QhCMhV4s4U6HFq2jBKS2IgiFDHEA0s1YVgDFc1yMorCDlDPTCKVP4hxc1lT/rYjvQckG281nX0WA9GMEm5ViYWiqGcJlZzmWiql45rkTYzSKeG3biKLU2iiTyaHsTPuxRK/fskK+lRKf6GbkQIx0wWFyY5v9JkbmmBN556i46VptmuY8bbnDx3gqHNA+zIRMmkt1KvlIiYOZLdj/uKoK8yo1AEgiaxxGau1lt8cOobTvx6kb1DL9HX3ctsa5bT9iz9uS3s3THOsTOTfHjqKH85Ecz0NlaKM9hW7f4B1ivzOI4FgSiFi1/j3rpBvvt1SqtZ5go/of3yLdkfT3L53HFsq4t0ch+LxQIXzh8iGMsQiefptFbuH6DbqaHcFrbncmh2EiM8Tk8qi66qHCm2OZ3eRTHXy2e3slQbZUY2ZzCMFzh8YRpPCxEMJ3A7/iLoqw4msoOAomo1iXhBnh1+GkkfVmCaY72PwFA/Nxt5JqJPsLN0kWiwh9GBEa6X5rDs9p0P4s98AVqNEtKz8UJdLHsNkmYbo1NlcuoPhqOHsIVJpz3AaKXFlJtn5+BNNmU7TNyo4ngOumcTimZ9AfpKsaaHsK0aybBJf3wL309/x9DGS0j3CvlGir5bgu3WIkNcIGzX2RIv8HPhONvT20hFk0TMboKR9P2LYCiaplX7E8+qMDq4i89/OMiZhUneffUdigsvUnczKAWmXiG3Mc/JwlEKi5cYf+VlnGYJt1MjEt/kC9B3oa6XZ5Dta8j0GAe+elOUrSJvj73Gc9mUygWzSKWx7FY5W6mKwxNHyce28uWBI8pbmUGPbCK5YcRXZxbvHWz6AhSyRsqZEJaeU7/XbVFunOVq4wv6k/sZyX2khNA4X/5UzK0eocd8n25zF8NxTQVUg4qxG7QIvjrJvo99Dk1CI6RKpL0ppIizRB9LnWsoTxIVPegiTEvdwGOFDeFH6dLKBGWRkjaOo2XApzoxhG8lJLFFjhX9GTJqmjTLiNjzSNnGdZoIdDKBJwmKNgk5i43Jqr4HVyQQKN/Ka416UOGKFEtiNwKJUg5CGASCybs7HKJUtDEUAW5TrU3XrUOwKhT6P+q823CsGQ7+f0PTQ8AHzx54wL8BjGG8sZiqLygAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDgrMDI6MDAuk0uoAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQ4KzAyOjAwX87zFAAAAABJRU5ErkJggg==", "name": {"common": "El Salvador", "cym": "El Salvador", "deu": "El Salvador", "fra": "Salvador", "hrv": "Salvador", "ita": "El Salvador", "jpn": "エルサルバドル", "nld": "El Salvador", "por": "El Salvador", "rus": "Сальвадор", "spa": "El Salvador", "svk": "Salvádor", "fin": "El Salvador", "zho": "萨尔瓦多", "isr": "אל סלבדור"}}, "GQ": {"currency": "XAF", "callingCode": "240", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAYBQTFRFTKAYT6MWT6MXT6MYjaTM3OvW4fDX4O/X3+3S3OfA2eOy2uOzppLP99fZ+9za+tvZ+drY9NXT2bm427y69tbV+trZ4jIt5jQr5TMr5zUsSoFyP5gKPpoAVGncSoByP5kHVWflVGjfVWfkPpkHPZoAPZkASoFzVWbkW2vnrLT0+/v/////+/fn7+Gq6t2l7uK17+O37N+r7+Ks+/jqVGbkWWrlqrPx+vr+/vz25eLS0M7PvMi9scOzysvJ5+LP/vz15ubmnLWcNZEzFI0TX6Jf5eblWGrlrLXy/f3/5OXkkLKQeJFrb5NjhrGH5ufm/v7+5+fnzs3Ou7Owtqypzs3P6enpWWvlqrPy+vv+/Pz84uLi39/fzM3NqZuSnYp+4+Pj/f3919fX1dXVysvLtq6pq5+Yy8vL0tLS3NzcVGfkWW3nq7b0+vv/6OnpxsjIwsTEyMvLx8rKycvL7e7uVGflWGXfnEV+nER+4CMd5CAW4yAX5CEX4yEYWmXdnER93iMfXpYxpgAAABp0Uk5T/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v6NeBQ9AAAAAWJLR0QrJLnkCAAAAAlwSFlzAAAASAAAAEgARslrPgAAAR5JREFUOMuNz2dTQjEQQNEFVMCCXcRu7MaCihV779gb2LA37D3qXzcJvDA4k+feT8nOmWQWiopLUEFpWTkOVlTiJBCkBIKUHOKkgCgpIUZGoZBV1TVmxSCXtWAxstpsVsufDMhlUnKKXeZwpqY5HfbEFCR19Q2NVNTU3NLqbWvvoAnFIfF1dnWLUU9vn79/YHBIC5UcHhkdG5+gekh8k1PTfDYzOze/sPg/DCwtr6yurQdMvt7Y3KJ0eycY2t0LBfcPtMtIR+lh+Oj45PTsXPfixeXVtRzdRG7v7iMPjxr49Jye4coUubKyc3Lz8qMXFSj3UuAujOX2eNTZCAz3+vb+YRYo98lMA6STEOMERDkOcY4B0jH4wjkG3z8ox34BWwca6rCG5yYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzcrMDI6MDDSHjJYAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjM3KzAyOjAwo0OK5AAAAABJRU5ErkJggg==", "name": {"common": "Equatorial Guinea", "cym": "Gini Gyhydeddol", "deu": "Äquatorialguinea", "fra": "Guinée équatoriale", "hrv": "Ekvatorijalna Gvineja", "ita": "Guinea Equatoriale", "jpn": "赤道ギニア", "nld": "Equatoriaal-Guinea", "por": "Guiné Equatorial", "rus": "Экваториальная Гвинея", "spa": "Guinea Ecuatorial", "svk": "Rovníková Guinea", "fin": "Päiväntasaajan Guinea", "zho": "赤道几内亚", "isr": "גינאה המשוונית"}}, "ER": {"currency": "ERN", "callingCode": "291", "flag": "data:image/png;base64,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", "name": {"common": "Eritrea", "cym": "Eritrea", "deu": "Eritrea", "fra": "Érythrée", "hrv": "Eritreja", "ita": "Eritrea", "jpn": "エリトリア", "nld": "Eritrea", "por": "Eritreia", "rus": "Эритрея", "spa": "Eritrea", "svk": "Eritrea", "fin": "Eritrea", "zho": "厄立特里亚", "isr": "אריתריאה"}}, "EE": {"currency": "EUR", "callingCode": "372", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRFAAD/AAD+AADmAAALAAAAAAABAQEBCwsL5ubm/////v7+G0fY8QAAAAFiS0dECfHZpewAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAA7SURBVCjPY2AYfEAQC8CuUgkLYDDGAhhcsACGUCwAu0oKBdOwAOwqy7EAhg4sgGEmFsCwCgvArnJABQESX7dJCCjvGwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo0OCswMjowMC6TS6gAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NDgrMDI6MDBfzvMUAAAAAElFTkSuQmCC", "name": {"common": "Estonia", "cym": "Estonia", "deu": "Estland", "fra": "Estonie", "hrv": "Estonija", "ita": "Estonia", "jpn": "エストニア", "nld": "Estland", "por": "Estónia", "rus": "Эстония", "spa": "Estonia", "svk": "Estónsko", "fin": "Viro", "zho": "爱沙尼亚", "isr": "אסטוניה"}}, "ET": {"currency": "ETB", "callingCode": "251", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAqxQTFRFKYwIKIwLKYwJKIwKLI0ILY0IK4wKLI4AK44AK40CGogGF4cIH4oDKo0GHIFFD3eDBnCiB3GfD3eAHoM+K40DHYkEGIcIiqYTh6UVk6oOcJ0hCnOWBmjVAGbYLHizInW6AGfWAWfWC3SQeaEakakQiKYV/8oj/sok/8sg9ccpMXu0F3K+b5N9AGHWZJCCdJZ3AF/aV4mOJHe3PoCr/soj/8si/MUh+8Uh+MQj/8sWaI+CAF/ZAGTRWImPPH+iip1rjJ1rInW1bZJ/AGnHAF7bfJd0/8sV/8Yh/sYi/8gd6L4vEW++L3ynNn6jL3uofJh0k6FknqZae5h1NX6kGHS2LHuqHXO39cMn/8cf/MUj/8sYu61PAGbQToeTvK5MnqNgsKxRdJV6jJ1slqFloaVexrJGRoSYAGfOzrRC/8oa/cUj+8Uj/8wWqaZbAGXQAGDaH3O4laFlmqNhCmjMFGnKu69Kh5xtF2+/AGLXAGjMvK1O/8sXuaxQAGrIZpCEZY+FRoOavrJFcZN9g5lypKhXTIaVX42JOX+hAGnK/8Uh/sUi/8ceLnuoZo+Fl6Jjp6ZasqtToKReUYaUGXS2SoWWJHWy9cIm/8Yf/sUh/8ch/MYk/84Wb5OBAGDbA2rEoKZad5Z5P4GfKHivmqJijp5qAGXMAF/dg5ty/84V/8Eh/sEh/8If8b4lLHmtDHPGU4ePAF3bWYyHM32jAGDUVYmOB3LKPH6g+sEg/8Ig8TQZ7zMb+DUU4DQkP1WYAG3WAG3XPIakIXy3AG7WAGvXTFGP6TMe9jUW7zMa7x0Y7x0X7Bwa9BwU9x8SqzdKW0+ENVifOFidYE6AszVE+h4Q8hwV7BwZ7yIY7SIZ7iEZ/B0O/hwM+B8S+B4R/xwM+x0P7iIZ7yEY7CIa6yIb////ZbFmDQAAAAFiS0dE47EGrooAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFqSURBVDjLY2AYBaQARkYmJkZmLBIsyICVjYGdg5OTg4OJjZUFFTBwIQNuHl4+fgFBIWERUTEuVMAgjgQkJKWkZWTl5BUUlZRVVMVRAIMaAqhraGpp6+jq6RsYGhmbmJqoIQMGMzgwt7C0sraxtbN3cHRydnF1szA3QwIM7nDg4enl7ePr5x8QGBQcEhoWHuHhjgSQFEZGRcfExsUnJCYlp6SmpWdkZuFQmJ2Tm5dfUFhUXFJaVl5RWRUVicvE6prauvqGxqbmlta29g50EzvhoKvbK6wnvbevf8LESZOnTJ02fUYnEmCYiQCzZs+ZO2/+goWLFi9Zumz5ilkzkQHDSgRYtXrN2nXrN2zctHnL1m3bd6xaiQwYdiKBXbv37N23/8DBQ4ePHD12fCcKYDiBBE6eOn3m7LnzFy5eunzl6skTKIDhGgq4fuPmrdt37t67fv8aGmB4gAYePrp+/dHDBxiA4QGRYFgpBAAoZOePTU0n9wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNToxMyswMjowMIu2eggAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MTMrMDI6MDD668K0AAAAAElFTkSuQmCC", "name": {"common": "Ethiopia", "cym": "Ethiopia", "deu": "Äthiopien", "fra": "Éthiopie", "hrv": "Etiopija", "ita": "Etiopia", "jpn": "エチオピア", "nld": "Ethiopië", "por": "Etiópia", "rus": "Эфиопия", "spa": "Etiopía", "svk": "Etiópia", "fin": "Etiopia", "zho": "埃塞俄比亚", "isr": "אתיופיה"}}, "FK": {"currency": "FKP", "callingCode": "500", "flag": "data:image/png;base64,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", "name": {"common": "Falkland Islands", "deu": "Falklandinseln", "fra": "Îles Malouines", "hrv": "Falklandski Otoci", "ita": "Isole Falkland o Isole Malvine", "jpn": "フォークランド(マルビナス)諸島", "nld": "Falklandeilanden", "por": "Ilhas Malvinas", "rus": "Фолклендские острова", "spa": "Islas Malvinas", "svk": "Falklandy", "fin": "Falkandinsaaret", "zho": "福克兰群岛", "isr": "איי פוקלנד"}}, "FO": {"currency": "DKK", "callingCode": "298", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAMNQTFRF/////P3+v8riADCagzBW4yci4CcjnjBJACyVoLPW+/z9/Pz++/z++Pr8vMjhADGani9JACyWnbHV+Pn8tMLeq7varbzbrLzbqrratcPeg5nHADOagzBVnS5IADCWbom/uMXfF0ChACmWAS6YAC2XACyXAzCXADumgC9X4ychmi1JADukAjGZAC2YnENfkCxMkzFPkjBPki9Oii5Quio32ygm2igmwyoziy1P5Ds34SQf4igj4icj4Scj4ici1igo1ygoNxdWNQAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAACaSURBVDjLY2CAAUYGJmYWVjZ2Dk4GLgZ8YFQhRQq5uXgggJeBjx+oUEBQiEGYBxOgmMiPz0QRUTFxMJCQlJKWYWWTlZNXkBDHBAyKSsoqEKCqpq6hyaalraOqq4IJGPT0DQyhwMjYxNTM3MLIEBtgsLSytoECWztTewczO1sbbIB4hURbTbRnRETFiQseogOcrCgcrOlxOCkEAAHTPL4sSadeAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjU5KzAyOjAwRE5AggAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo1OSswMjowMDUT+D4AAAAASUVORK5CYII=", "name": {"common": "Faroe Islands", "deu": "Färöer-Inseln", "fra": "Îles Féroé", "hrv": "Farski Otoci", "ita": "Isole Far Oer", "jpn": "フェロー諸島", "nld": "Faeröer", "por": "Ilhas Faroé", "rus": "Фарерские острова", "spa": "Islas Faroe", "svk": "Faerské ostrovy", "fin": "Färsaaret", "zho": "法罗群岛", "isr": "איי פארו"}}, "FJ": {"currency": "FJD", "callingCode": "679", "flag": "data:image/png;base64,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", "name": {"common": "Fiji", "deu": "Fidschi", "fra": "Fidji", "hrv": "Fiđi", "ita": "Figi", "jpn": "フィジー", "nld": "Fiji", "por": "Fiji", "rus": "Фиджи", "spa": "Fiyi", "svk": "Fidži", "fin": "Fidži", "zho": "斐济", "isr": "פיג׳י"}}, "FI": {"currency": "EUR", "callingCode": "358", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAADlQTFRF////jrnvAGHaAGLaAWPapsjy1eX52ej6dqvri7juFm/dDGncDmvcFW/dC2nc1OX52Of5dKnrAGDaX9TlqQAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAABXSURBVDjL7dG7EoAwCETRFTQx8e3/f6wzDqRiDFVscurbsABqIH6NE771sE0YRJw1TDEYkNXCYs2bAaS0YzKBnf4M3ceU+/fKPIc4r8rgRbqdv+5hk/ABSuILPw2ihQEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NTMrMDI6MDDgPh/MAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjUzKzAyOjAwkWOncAAAAABJRU5ErkJggg==", "name": {"common": "Finland", "deu": "Finnland", "fra": "Finlande", "hrv": "Finska", "ita": "Finlandia", "jpn": "フィンランド", "nld": "Finland", "por": "Finlândia", "rus": "Финляндия", "spa": "Finlandia", "svk": "Fínsko", "fin": "Suomi", "zho": "芬兰", "isr": "פינלנד"}}, "FR": {"currency": "EUR", "callingCode": "33", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRFACZ/AymBABl3qbbU////+/z9//z8+7G58gok8xsz8xgwxtlaVAAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAeSURBVCjPY2AAAUZlVxcgSKmYtQoEGEYFRwVpKQgAg3q7n/McyNsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDcrMDI6MDDY2ztBAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQ3KzAyOjAwqYaD/QAAAABJRU5ErkJggg==", "name": {"common": "France", "deu": "Frankreich", "fra": "France", "hrv": "Francuska", "ita": "Francia", "jpn": "フランス", "nld": "Frankrijk", "por": "França", "rus": "Франция", "spa": "Francia", "svk": "Francúzsko", "fin": "Ranska", "zho": "法国", "isr": "צרפת"}}, "GF": {"currency": "EUR", "callingCode": "594", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRFACZ/AymBABl3qbbU////+/z9//z8+7G58gok8xsz8xgwxtlaVAAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAeSURBVCjPY2AAAUZlVxcgSKmYtQoEGEYFRwVpKQgAg3q7n/McyNsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTQrMDI6MDBOEUSGAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjE0KzAyOjAwP0z8OgAAAABJRU5ErkJggg==", "name": {"common": "French Guiana", "deu": "Französisch Guyana", "fra": "Guyane", "hrv": "Francuska Gvajana", "ita": "Guyana francese", "jpn": "フランス領ギアナ", "nld": "Frans-Guyana", "por": "Guiana Francesa", "rus": "Французская Гвиана", "spa": "Guayana Francesa", "svk": "Guyana", "fin": "Ranskan Guayana", "zho": "法属圭亚那", "isr": "גיאנה הצרפתית"}}, "PF": {"currency": "XPF", "callingCode": "689", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAnZQTFRF3iAQ3yQU3BAA2w4A2gwA2g0A2w8A3A8A7pCI7o+H75CI7o+G75iX8JWR86CX9ZyT8JSP75eX75CJ/////Nqs9+XD27K3wrC18eDA/t+1//78/vv7/vr4+tOt9LZ69ZU72XU40QAF283S+KZV7n8g86pi98GM/vn3///+/ObO9bRy9H8J/b902IhZ7QAA1nd46KJU+Kxf9YMN9Kld/e7e//7+8I0y959Fw4Ze5pQ+3qSI8wAB7wAA1pN27ZpBwnhJ85k78Ysq//79/OjT9bBr9qA+ok1F14dE3ZZo8gAE9wAD2oRW4ZRIn0M78po59q9o/OXO//37+MWS73gA8q1awD896IpF6LSJ4Ccu7AAC3oxh76lovjY27qJO8HoD9Khb+daz9KZZ77NpyEE/yIlrzcWxwsvMrpOTuaOS0KiOxTcz6qdc9alc98mZ+8CE9HEA+Z8xz0pDinKAcHyGdniBfYmSd4GJkIWSzT409ZMi9XMA+rp5/v7+6uXi6821qa6ysS00rJisRoTJkqPIl6bJQnrArazCsSQopKOm68206ODb/f7/3+j1SnTAXZXaplhq6AYCwTUyXXChYHyvuzs56AkFqUZWXZXZSHPA2+X0/f7+/v7/7PD2gZrJSW+0aY/Fznx/bUh1XIbFYYrIcleDzHJ2bI3BRm617O/23+byaIjCPWaxOnbDWYrLTnO3THG2VYXHK2u+OWSxZIXA//z7/Pn6///95+v0gJjIVXOzO1+qUXS2V3i4OF2pR2iueZLF5urzzuP2Wo3PUobKUITJZZXT2ev67o+I6oyG9JSK/56P5omE4YWC2g4A2A0A3REA3hIA3iMUzvqPKwAAAAFiS0dEEwy7XJYAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFlSURBVDjLY2AYToCRSMDAhA6YmVlY2ZjZ0YUZOJABJxc3Nw8vH7+AoBAnJxeKFIMwOhARFROXkBSWQhNmkEYGMsKycvIKikrKKqpqwuooUigmaghramnr6OrpGxgaGZsAucgmInNMhc3MLSytrG1s7ewdHIVRVCIrdBJ2dnF1c/fw9PL28fXzDwAKYFcYKBwUHBIaFh4RGRUdExsXj+IhVBMTEpOSU1LT0jMys7JzcoFOwWViXn5BYVFxSWlZeUVlVTVQALvCGuHauvqGxqbmlta29o7OLqAAdoXdwj29ff0TJk6aPGXqtOkzZgrPwqFwtvCcufPmL1i4aPGSpcuWz10BFMARjjXCK1etXrN23foNGzdtXoliIGoUbtm6bfuOnbt279m7b/8Bp61bcEYh2J0HDx0+cvQYkIGWKI4jA04uzhMnT50+c+rkCSATRQoz4Z49d/7CubMYwtiywsWL2LLCcAIAa8yUOqaO+7AAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTkrMDI6MDAvxiVGAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjE5KzAyOjAwXpud+gAAAABJRU5ErkJggg==", "name": {"common": "French Polynesia", "deu": "Französisch-Polynesien", "fra": "Polynésie française", "hrv": "Francuska Polinezija", "ita": "Polinesia Francese", "jpn": "フランス領ポリネシア", "nld": "Frans-Polynesië", "por": "Polinésia Francesa", "rus": "Французская Полинезия", "spa": "Polinesia Francesa", "svk": "Francúzska Polynézia", "fin": "Ranskan Polynesia", "zho": "法属波利尼西亚", "isr": "פולינזיה הצרפתית"}}, "TF": {"currency": "EUR", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRFACZ/AymBABl3qbbU////+/z9//z8+7G58gok8xsz8xgwxtlaVAAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAeSURBVCjPY2AAAUZlVxcgSKmYtQoEGEYFRwVpKQgAg3q7n/McyNsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDcrMDI6MDCzU16FAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjA3KzAyOjAwwg7mOQAAAABJRU5ErkJggg==", "name": {"common": "French Southern and Antarctic Lands", "deu": "Französische Süd-und Antarktisgebiete", "fra": "Terres australes et antarctiques françaises", "hrv": "Francuski južni i antarktički teritoriji", "ita": "Territori Francesi del Sud", "jpn": "フランス領南方・南極地域", "nld": "Franse Gebieden in de zuidelijke Indische Oceaan", "por": "Terras Austrais e Antárticas Francesas", "rus": "Французские Южные и Антарктические территории", "spa": "Tierras Australes y Antárticas Francesas", "svk": "Francúzske juŽné a antarktické územia", "fin": "Ranskan eteläiset ja antarktiset alueet", "zho": "法国南部和南极土地", "isr": "הטריטוריות הדרומיות של צרפת"}}, "GA": {"currency": "XAF", "callingCode": "241", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAADBQTFRFNqEAN6EAMqAARKYA8eIA/+gA/ucA/+cA/uYB/+kA7t8NEXWvAGu/AW67AG28////LJeFrAAAAAFiS0dEDxi6ANkAAAAJcEhZcwAAAEgAAABIAEbJaz4AAABESURBVCjPY2AYfEAQC2BQwgIYjLEABhcsgCEUC2BIwwIYyrEASgU7sACGmVgAwyosgGE3FsBwBgtguIsFMLzDAgZWEADDTRb4erT7RQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDozNCswMjowMOP2KMUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MzQrMDI6MDCSq5B5AAAAAElFTkSuQmCC", "name": {"common": "Gabon", "deu": "Gabun", "fra": "Gabon", "hrv": "Gabon", "ita": "Gabon", "jpn": "ガボン", "nld": "Gabon", "por": "Gabão", "rus": "Габон", "spa": "Gabón", "svk": "Gabon", "fin": "Gabon", "zho": "加蓬", "isr": "גבון"}}, "GM": {"currency": "GMD", "callingCode": "220", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRF/6amjIzRjY3RpNuk/wAA/wEB////AACYAACZAZkBAJkAYwm0MwAAAAR0Uk5T/v7+/rpKSvwAAAABYktHRAZhZrh9AAAACXBIWXMAAABIAAAASABGyWs+AAAAOklEQVQoz2NwwQIYBlYwFAtgwArSsAAGQSyAoRwLYOjAAugniNVJSlgAdm8aYwEMM7EAhlVYwMAKAgBVI/8BRz5IwQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo0NCswMjowMOkzIdwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NDQrMDI6MDCYbplgAAAAAElFTkSuQmCC", "name": {"common": "Gambia", "deu": "Gambia", "fra": "Gambie", "hrv": "Gambija", "ita": "Gambia", "jpn": "ガンビア", "nld": "Gambia", "por": "Gâmbia", "rus": "Гамбия", "spa": "Gambia", "svk": "Gambia", "fin": "Gambia", "zho": "冈比亚", "isr": "גמביה"}}, "GE": {"currency": "GEL", "callingCode": "995", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAMBQTFRF/////7u7/3h4/3l5/35+/wAA/+/v/+Xl//n5/5OT/zc3/+Dg/+rq/62t/2ho/xoa/5yc/8DA//7+/8/P/x8f/w8P/wUF/xAQ/2Bg//Hx/83N/x0d/7i4/9bW/52d/05O/+Li/01N//j4//X1//39//r6/6Cg/4CA/4KC/z8///T0/0BA/3p6/3x8/z09//b2/5+f/1FR/9jY/7q6/+np/6qq/2Zm/5mZ/729/5KS/zU1/+7u/+Pj/7m5/3R0/3Z2xZZBSQAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADwSURBVDjLxdNrF4EwHAbwLsOoFIUSIRVyySWXSvX9vxXe2f45p47jeF5uv9Pa9oxhfh+W43mOLQFR7Rn0eb7ewBDiRh3AZkuAUGg1KSZKbVnpdEnY7ShyWxIJqGq9/kCnoT7o9zSVgMaQM0djGo5HJjc0qMWtyRT+43Rmgc3YcwdCZ25XP0fXQ4v3LFcvuFoSg8hzGX9dK5G1/0+Irc32PWj3mtghYnBj4eo1w0HRgQfwS/tD0RUe9hQzVN5kj3QpjieTV8lSlK5ZeL7I1xsNb1f5cg6pxaO46CnEEdhMci96XPekes1A0izPs7QE/DYPxwQhkdcwJgAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NTgrMDI6MDDiOUs2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjU4KzAyOjAwk2TzigAAAABJRU5ErkJggg==", "name": {"common": "Georgia", "deu": "Georgien", "fra": "Géorgie", "hrv": "Gruzija", "ita": "Georgia", "jpn": "グルジア", "nld": "Georgië", "por": "Geórgia", "rus": "Грузия", "spa": "Georgia", "svk": "Gruzínsko", "fin": "Georgia", "zho": "格鲁吉亚", "isr": "גאורגיה"}}, "DE": {"currency": "EUR", "callingCode": "49", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACdQTFRFAAAAAQAAEQAA7gAA/wAA/gAA/wEA/xAA/9YA/+oA/+UA/+YA////X5h2TAAAAAFiS0dEDIGzUWMAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAA9SURBVCjPY2AYfEAQC8CuUgkLYDDGAhhcsACGUCwAu0oKBdOwAOwqy7EAhg4sgGEmFsCwCgtg2I0FDKwgAGlOyeEHSqKvAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjIwKzAyOjAwNNFndgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToyMCswMjowMEWM38oAAAAASUVORK5CYII=", "name": {"common": "Germany", "deu": "Deutschland", "fra": "Allemagne", "hrv": "Njemačka", "ita": "Germania", "jpn": "ドイツ", "nld": "Duitsland", "por": "Alemanha", "rus": "Германия", "spa": "Alemania", "svk": "Nemecko", "fin": "Saksa", "zho": "德国", "isr": "גרמניה"}}, "GH": {"currency": "GHS", "callingCode": "233", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAbBQTFRF6AAG5wAG7AAG6wAG6AEG5wEG7wEGyAAFzwAF7gEG5AAF+wAGbAADgAAD6hEI6REI6RAI8hIIJgIBPAMC9xII5xAI/eMc++Ec/+sd074YAAAADAkB4swZ/+kd//ge//kf//of+/Qe//8hiogRnJoT+/Ue//ke//wf//Me//Qe/PAe+Owd//ce//Ue/vQeOzgHSkYJ/vIe8uYc/vMefXcPHRwDMC4GMS8GLSsFKScFBQUBBwYBJyYFKCYFLCoFJiUEFxYDiYMQ//Ye/PMee3UOAQEAk4wR/vUe/fUeycEYJCIEBAMABAQAMC0G1s8a//8g+/Ad//4g8uwdV1IKAgIAZF4M+PMe+O0dnp0UBAQBAgMBAwMBpqQV//Yd/fQd//8fS0kJAwQBUE4K/PMd8Owh7+oh9fAi3NgeEREDAAABcXISbnASExMD3tof9PAhLoBFLn9EMYlKIlkvAw4IGFk1PJBIPI9IAw4JG3ZJG3VIHHxNCCQWCB4SHnNFIX9MGHJICSATIHlIIHhIIHpJHnJEFU4uIHxLIHtKH3ZHIH1LFlAwH3hIIH5LH3dHH3dI////gfZjOgAAAAFiS0dEj/UCg/kAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFUSURBVDjLY2AY5oCRkUh1TMzYVLKgA1Y2dg5OVgxhTJ1c3Dy83FyY4nxogF9AUEhYRJQfXZxBDA2IS0hKScvIiqOLM8ghgLwCkFBUUpaSUlFSVVNTU1dDkmTQgANNLW0FHV09eX0pKQN1LUNNXSMthKQGgyYCGJuYmplbWFpJSVnb2NrZOzjqGCMkkRQaOzm7SAGBKwi5Slm5KbhrYFWoqeGh5Okl5Q1S7CPl6+fvjKQORaGmXkBgULBUiBQQhYapByBLaTIYIwO9cKUIqcioqGipGP9wPRQphlgUEBefIJUoJRUplRSfjCrDkIIMUtPSM6Qys7IzpXJy81JRpBjykUFBYZFUcUlpWUm5VFFhAYoUQwUyqKyqrqmtq6+vq22orqpEkWJoRAJNzS2tbe0djY0d7Z1dLc1NyHIM3aigp7sXRPX29aBJoCvsxWDgUIgTDAWFADjaJV+3MAkgAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjQ4KzAyOjAwLpNLqAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo0OCswMjowMF/O8xQAAAAASUVORK5CYII=", "name": {"common": "Ghana", "deu": "Ghana", "fra": "Ghana", "hrv": "Gana", "ita": "Ghana", "jpn": "ガーナ", "nld": "Ghana", "por": "Gana", "rus": "Гана", "spa": "Ghana", "svk": "Ghana", "fin": "Ghana", "zho": "加纳", "isr": "גאנה"}}, "GI": {"currency": "GIP", "callingCode": "350", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABKFJREFUSMfFV02LXFUQPafuu69fz/SMnYnRSIwgAYXRjEQxoBJ0o6ioEAVFRMGPlaAb3SmSv6DuA7pU0FVcKQZ1o4gYMzHBJIZ8OcaEGae7X3+8e2+Vi04U40xPehSsxePxePecqnOr6talmeH/MPlfWAFk61hjKcEMJIDhC50bF4RrS21mZiTNjABEVvvtojfD59Ctf0V8GTr52wcfhuXfXaOBGC2pq9eveerJvzT4r6Q+/OJLqNd7R49OzM6WBw7c9O47i/v2nXn/PT/ZMDXtlTMPPjTz2KOHn34mv2Fr58cfG3Nz5fz87N69xfVbRngzMmJViMw//sTCxx8VU9Ox23MbN+787ttUlt/fs6vmHET63e6tn+xr3n3XN3O3dQ7+4KemQ7s1teOOHV/szxqNEcSjstpUAczcd99UXuTNZpHnmx+4v9iyJZy/IP2+qaom6fcG584BmLpzpy/qWbOZ5cWGhx/KGg2LcYT4a5dT7/DhpCoxUdg/fhxA/9hR6/XoRCgIsTc/D6A6NO9dVoupRoafjgLgyC0fRTwskt6x4857qpr3YeFXBcLJU7mZgDTzzqXjP1ftdv/0aea5aopZ1j9z2swwssbcnj17Vt5dsnvixJFnnxscOkRAiCgOZScs/Nr+fH9cXqZzaprEcXm5c+TI4PsD8JlTM+fY7S5++unk7TvyTZtMdcXQV04uU6VI98SJI7vuRVky96ZGwAComiq9x6WFZiZCmAE0gKSlpCHc/NWXU3Pbh1BXKjVFLMaJG2/c+uYbodNRkYyMAM02vPpK1mhoSkIIGWLwMzMbXn45Dao+KUAQie32xheen5rbbilxlYazRnJVnY4zcyCAjIRpvnmz5Z5mBgJwoAG1664TkRwgkBEEYr8/GnkN4l6rRSADKrOCTEkH/X6kFMNMgBXCEEIY9ANZJ7owDxjZb7VGI6/RuYp2e4n83ZRmyawwk6WlOtAzq0wJJLCWoi4uFSKLqmZoq3kRX5YYWVGrR0wCCJ2OkQJMiyRYAqqFBY2xIgrQgwmwmHpnzxpgwDRJGESqTmmXQMaMOCU4F9ptiNTAHJaBJlKeOqUxCKVGJiCRUVM8eTIBHizIACZhVZYWI0mIrEi/asTMMgAyGOTeJ+fUuSRSq9XCmbO5gT5LlASBzwpxdvrMhPfqnGZOnWQ+r4egKY3oIf+oYzOQsdU6+9rrWnZbn32Wzp8PWebMEuCBivRASoqGN6G0qsxJZZaTlZkHIikpyeTkzO7dpnrtnrcmtm0bnjd/k9pivIyY3luMamaw5uO7/5wuhg2EgMGYXGq26VQuNDWLBA2QYYcZwqhpu5WqYClhOLSojoz4SswM5KB1zHRQNG+BKTj27MYLb7+zMnhKqy8ylN5mzyNLPLgZEwG2avbSuYtaXfb9YJaP6SoswM+CW3Oq6bkQfgD9CtCjLcs2bRqbeAC3gcXkIKqrGj7bqKyNT3x5cl2JeYSvTR+pWV30mz6vpoWxMdYzV0PBSQx6Obvmi4tT77g2/k1CKAPt3DwTHr3KMa2PdV3EBohZicGSt4zjbu2fNr7UZurd9C+L1Qe9RLgc67v1/QETyouHxE7rGgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNToyMyswMjowMAU5fesAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MjMrMDI6MDB0ZMVXAAAAAElFTkSuQmCC", "name": {"common": "Gibraltar", "deu": "Gibraltar", "fra": "Gibraltar", "hrv": "Gibraltar", "ita": "Gibilterra", "jpn": "ジブラルタル", "nld": "Gibraltar", "por": "Gibraltar", "rus": "Гибралтар", "spa": "Gibraltar", "svk": "Gibraltár", "fin": "Gibraltar", "zho": "直布罗陀", "isr": "גיברלטר"}}, "GR": {"currency": "EUR", "callingCode": "30", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAK5QTFRFO4X2pcf7psj7o8b7pMb7H3T0WZj3Wpn3V5f3lL36/f7/////6vL+baX4WJj3W5r3AGHzAF7zWpn3/v7/////4Oz+H3T0AGDzAWLzAF/zW5n3AFvzWpf3H3L0AF3zW5f3WJf3lb36/v//6/P+bqX4WZj3dKn4pMf7AmLzAWHzpMb7AWDz+/3/5O7+nML6IHX1G3L0OoX2HXP0ZJ/44ez+Wpj3H3P0W5j3/f7/HnT0fFGBNwAAABB0Uk5T/v39/v7+/f7+/v7+/v7+/vURJdcAAAABYktHRAsf18TAAAAACXBIWXMAAABIAAAASABGyWs+AAAAzElEQVQ4y53R1xKCMBQE0LV3BRKMvXfFXv//x0wy+hIvesfzwOzDApMsPMMPhAzryvsC/xcbNLfYRCJJcot+S0gSfKPd0cVuz6R+XDEwBsORHE+mOk1nqTQJwhrpd2yQ88WSBPcXq7UiIbTGUm4iE6Ltrk6Cfe4nQkaHo0mn8EyC/W7vok99bZp0izu1e4/BXZDcospkaR9bK5/mLhMLOSNfKJbKlarNNdprGfFeRo9Eg2R6LfMbHkxQTGgwIcnEPzW7mGbCkgkeE7v4BBtYYvXw4HRMAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjQ3KzAyOjAw2Ns7QQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo0NyswMjowMKmGg/0AAAAASUVORK5CYII=", "name": {"common": "Greece", "deu": "Griechenland", "fra": "Grèce", "hrv": "Grčka", "ita": "Grecia", "jpn": "ギリシャ", "nld": "Griekenland", "por": "Grécia", "rus": "Греция", "spa": "Grecia", "svk": "Greécko", "fin": "Kreikka", "zho": "希腊", "isr": "יוון"}}, "GL": {"currency": "DKK", "callingCode": "299", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAQJQTFRF//////7+//39//z8//v7/vv7+97e+MvL+c7O/Ofn+93d7XFx5CQk4AUF3gAA4AoK5TQ08Y6O/fHx8YuL3wcH3QAA3gEB3wAA3wEB4hwc9rm57GZm3AAA3wMD4AQE3wIC85+f74KC4AYG9729+c/P3wUF5C0t/vn56mBg86Ki/e/v4hkZ6E9P+MjI4h4e/vj4/fLy9a6u4AkJ4hcX4hUV4RIS4yAg/vb2/fDw4hYW6lhY/e7u/vT0/OXl5Ckp4RER5TY2++Dg4Q8P8pyc6lpa7nl5/vr65j4+8ZOT6ltb7W1t/vX150BA8IiI+tbW+MbG7Gpq4AsL4xwc5TAw5S0t4hQUVuhhJwAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFRSURBVDjLzdLZVsIwEAbgSdLiHhWZJippXMAFFAq4UBX3fV/f/1UM9JyeFEq58cL/pjff+SedBOB/hFBGyWhGnUi7IywDcHNj4xMECMuaymByanqG89m5+TxkSAoLBfSElMLDxaXhksJyUflaCiGVz/nKMMlgtbhmVBSlvfV0SaFUlrEzkm9sAkkt3EJfWPFxe6CSEEKhUhVKWvFxBxySSFS4i5Lb0Vir988OgqACjWZrL5H9g9xhO7ADYRhWm0fsOG+nfdI5PWuFdsAMUnjeN4fCRYF7idOAOboWlx2g9sldaKAvE4He3vAKHLvRgWtUIpEe1GbBhNh7LZWFTIGm8gZuY8kIu0Mt0qDQeA/M7T5vwlwgD/2DY2g6H5+iXyeQex50MZQKay+vdXMBb+8fKS6GptND9fn1/cNRpjgLCu33Ltx8RDbsWq1T1QDMyN/DX8xHSUJAyGAPAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjI0KzAyOjAwwJ5DZQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToyNCswMjowMLHD+9kAAAAASUVORK5CYII=", "name": {"common": "Greenland", "deu": "Grönland", "fra": "Groenland", "hrv": "Grenland", "ita": "Groenlandia", "jpn": "グリーンランド", "nld": "Groenland", "por": "Gronelândia", "rus": "Гренландия", "spa": "Groenlandia", "svk": "Grónsko", "fin": "Groönlanti", "zho": "格陵兰", "isr": "גרינלנד"}}, "GD": {"currency": "XCD", "callingCode": "1473", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABFhJREFUSMedl12IVVUUx39r73PuOHnHvDmjmSjiqEUmlvTQx9g3pGVRmNVTJIEi9hA9CGn0UCBE9RBEGCQhaR9EPpQTFaVPYxYkYRLazbK0sfHbHOfOvefsvXo490x3uufccVps7j13r3XWf33ttdcVJZ9CMFDN4Qq0wXD+6+1QBZ/NNNkagTa4Dx6DYsPmCFlYAE/B9CZWIjwVVsGNYHPM0uYlKGg72ov+gF7VsNko8Bx6Br0fBTUN3OS5Bx1AX256N11Bti1AFfcKtg3ONXlswMHnUIYDKfc/fh9GV6P9mSEFkNwcC4BTTFYs6wKafjZRklkjDZ5cIvAQ9K7nkUXoUF3FKEkFk7reZI9TLJwr8vVOVmxHDdJUYhmhTty4TFhf4ovpvD0Nr3hNzR+LnGItlYhlJyiVWAE+q8KCXAVKscaWMmePs62bdsF57FjYsRJYjlZ5qMw+eDTOlczLPQqxQBs7znFHmb88NiSWNMJZKxKCAt9XuKnMvgoUyMfNB06wUQoh3w1y80EOVAgCYs2WjJQwZOdZbv+Z/hptQZqzcQOnnFixAUeq9Bzkq/MEIVFyEh048PXHMGTzAA/8QsVjLHFL1JbASUbTQ2Utf3uWltl6kjDAG5gMnfW+Flg2HmXtEawBwStjFmJTcSXnsgsm4obB1VW4tKqfPMzvMS90wXvoIeQ2hntYXWbbSYIQp+hYkDkeCwI6C9nBq0tYXkRT432StYAX+/lxDbxEdAie5oNNbLtIwY4DNTfUojCBZSU+XczjV4CrHyTxaDv37Gfhr/hnCDfBCh7cxdTT1ALGAZvpMcBUdDLxOviY66/8N+ACCKUIzqMz4VYo0nGKiQ7M2HltCZxYvR85hSxm7/v0DYJBFcALVNk9lzNF7HbcvdDHnnn81kkQ4ceD3ATsQaAflvP63SxfwydHkAAHkjT9YVYuYPKbUMHsh1kseoNlk4gjQhmH01m9WhFBj7LlJ053Yyu4BFKJPa/N5tkudAZ8iLmITqJUo7fEWuGtAYIAd2m5zunVihhsAA4FKzhPm/DuHFZ2EsfYIRAIkQt4UMPm2cws8PwxrK3f/OMM9Qi0xylAIDhHV8Cu+azsJIoIQJJb2oHBGAzEMRtnsHUOKJo0H/1fwHUSajHXTGDP1dzSUU9kI3fkOxCiiCe6+HIel1tilzsjjA0cAjXuLNI3n7kF4hqBgs9doRJXuauDvnl0hxC1uHTzWSJcsDw8i4+mYwWvrbQ0qnPKtVP45gZ6Bhg0rSSbowswpGwYZlUNc5BYsJkDclYwDUTKFGF3O59VcqMqrSrA4jyi+cNeQjnDnggmaygbsS9XqZ+G7caASH1+HbVCZCGiWVzBglyHn5DvVe5APwV9B+1F54we2SX9uQ79Fl2absrogX4JuhfdgIbZM33+QH8a/oAKHB8dTwULDgbgRMqVphL4E45BP0Sp/KXmOIACJOcn839bB9QghMEs7kSIoQAXstX/A5wl0MI5RvbRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjM2KzAyOjAwdGk57AAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozNiswMjowMAU0gVAAAAAASUVORK5CYII=", "name": {"common": "Grenada", "deu": "Grenada", "fra": "Grenade", "hrv": "Grenada", "ita": "Grenada", "jpn": "グレナダ", "nld": "Grenada", "por": "Granada", "rus": "Гренада", "spa": "Grenada", "svk": "Grenada", "fin": "Grenada", "zho": "格林纳达", "isr": "גרנדה"}}, "GP": {"currency": "EUR", "callingCode": "590", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRFACZ/AymBABl3qbbU////+/z9//z8+7G58gok8xsz8xgwxtlaVAAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAeSURBVCjPY2AAAUZlVxcgSKmYtQoEGEYFRwVpKQgAg3q7n/McyNsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDcrMDI6MDDY2ztBAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQ3KzAyOjAwqYaD/QAAAABJRU5ErkJggg==", "name": {"common": "Guadeloupe", "deu": "Guadeloupe", "fra": "Guadeloupe", "hrv": "Gvadalupa", "ita": "Guadeloupa", "jpn": "グアドループ", "nld": "Guadeloupe", "por": "Guadalupe", "rus": "Гваделупа", "spa": "Guadalupe", "svk": "Guadeloupe", "fin": "Guadeloupe", "zho": "瓜德罗普岛", "isr": "גוואדלופ"}}, "GU": {"currency": "USD", "callingCode": "1671", "flag": "data:image/png;base64,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", "name": {"common": "Guam", "deu": "Guam", "fra": "Guam", "hrv": "Guam", "ita": "Guam", "jpn": "グアム", "nld": "Guam", "por": "Guam", "rus": "Гуам", "spa": "Guam", "svk": "Guam", "fin": "Guam", "zho": "关岛", "isr": "גואם"}}, "GT": {"currency": "GTQ", "callingCode": "502", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAvRQTFRFAGb/AWf/pcn/////7e3t+Pj4pMj////9/Pz8bW1utLO0///+o8j+AWb4dKXMtcm28/f09fTyVVlKQU1FxcbG0NvPdqXEBGj3A2PDPn5wZo1s0dfT+vr7/f38/Pr3s5N/SE4xPFRFv8C/+/v76Ojogp+HTohxCGW6AWXpBWKxdaK03ebczMzNsa6duK6A5tiT5NCIvalfpKlugIJ8oaGhysrK0d7SV4+UB2KnAWXnAWXrBF2lQIB91ePV//7/zsWYoZJKw7FcxLNeyLZg1MmUra2slJSU8/Ly7fPtTomBA12dAmTQAWXqA12rhbC9/v778+3N3cpv4c1s4s5t489t2ch38Ozdr6+wyMjI/v/9eKagBFuJAmTSAGb+AmTXBmCWpcfm/v357eCh49Bt5NFv5NBu8+eswcC/n5+fh7G0BltpBGK4A2PGpsfb/f399ff59vDc3MNy07Nez6xb0K5c5tJ+rauekJGSl7uyCFxQBWGjAGb5A2LDBV2ipMTJ/v//6erls6l/t5pV0a5b2bxi4c50iod1qamqpcbSBV+kBWCYAWXxA2PLBl2Gkbex6N7Vm4tYuKtl4tOC4dSL59mWwqh0eXRv09PU9vr2a56YCFxqBWGZA2TPCV5sYpZ94+3k9Pj4zMC/4dbO8vDlx83K0NbX/v79xrOxcWlo9Pb2ssy2KnJcCF1wAmTRAWXyBmTFaZuomLGWu6ej6Ozs5urt3OHl1dvg6OzunZ2emZiZvq+oqsOtU4qCB2CGAmTWAmj8Y5SzbEw2hG1b4+re2N7a3eLm/f3++/z8sbe7cHh1pLWgdUU2jXlnQYGBAmXlAmj/V1WCdz8ve5h2s7uJhqBx2ubd4ODgjZGNf49lg59kXWhHZh4OS2R9Amj4hJXJtrOjo76gmLCHhqqMzdrQ6urqx8bHnaefdJp2iKJwcptxqZGDkafc/v76vNLAkraay9rP+fj46+vr4N/f2+Hchq2PgquLqsaw/v75+vz68fby8PXx8fXyWpUoygAAAAFiS0dEAxEMTPIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFqSURBVDjLY2BAAoxMzEiAiZEBFxiOCllYCSpkYwcq4ODkAqnj5sGtkJePX4CZWVBIWASoUFRMHKdCCUkpaRlZOXkFRSVlFVU1dVzqNDS1tHV09fQNDI2MTUzNzC0scSi0sraxtbN3cHRydnF1c/fw9PLGodDH18+fOSAwKDgkNCw8IjIqOga7uti4+ARm5sSk5JSU1LT0DObMrGwcJubE5zLn5RcUFhWXlJaVM1dUVsViVVddU1vHXN/Q2NRc1NLa1s7c0dnVjd3qnt4+Zub+CRMnTZ4yddr0GTNnza7GrnDO3HnzFyxctHjJ0mXLV6xctXrNWhxuXLd+w8ZNm5m3bN22fcfOXbv37N2HK8T3Hzh46PCRo8eOnzh56vSZs+dwRuH5CxcvXb5ylfna9Rs3b92+cxe3wnv3Hzx89PjJ02fPX7x89fo8ToWMTG/evnv/4eOnz1++fvuOP+H++JkHSoy/fg/yzEUnhQD28oLfgbT3NgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo0MiswMjowMIrjFOYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NDIrMDI6MDD7vqxaAAAAAElFTkSuQmCC", "name": {"common": "Guatemala", "deu": "Guatemala", "fra": "Guatemala", "hrv": "Gvatemala", "ita": "Guatemala", "jpn": "グアテマラ", "nld": "Guatemala", "por": "Guatemala", "rus": "Гватемала", "spa": "Guatemala", "svk": "Guatemala", "fin": "Guatemala", "zho": "危地马拉", "isr": "גואטמלה"}}, "GG": {"currency": "GBP", "callingCode": "44", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAKVQTFRF//////7+/e/x6iI85wQq5wgv5wku6SA86i8l6zkq6zMp/vHx6Ro97U8i+d8X+d8W6iU75gAs8Xoi+ugV5gAr8G0k8HEj8HAj//39/e7w/vP0/ODk6iQ66iU86Rkt5wYu8HEi6A0p6A4p5wUq5gEr5QAx72cj+ukV6BIu6BQu5wkv6zoq+d4X8Xsi8G4k9KId+uMV6BEt6BMt+d4W+dwW7U4i6zIpl5NLTgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAETSURBVDjLxZRZj4IwEIBrUUAOBYZDFIUVBS8Uz///07bHYjC7hkl82O+hybRfOpmmM4S06RGq9AeqOugrlAXvYaKmDw1jqGtdomnZo/F4ZFtm142O6wF4rtOZ2vEDgMDHiCFAiBIjgOgTsddmQuJGjMnk5Qh/I5WYYo2ns0SKyWwatw4oJYpAsxzBPFmkGUCWLpK53LE0aZAvxjLXbXflC9J1UZbFOpXRyrX1fMkdsmFsd/tDdQwjTlac6vpUZCIIj9Vhv9tyh5wZF9W4QkNZ3251+QyvhnrhDl5sUnvBX6kD75n6p5j7u2LuTTHo50E/+D9+ig9EdCtgm8utAKrudsUPADZSHg/ESKFKzodU/mtIfQOArTGdPqeFsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDozNSswMjowMEWBI3EAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MzUrMDI6MDA03JvNAAAAAElFTkSuQmCC", "name": {"common": "Guernsey", "deu": "Guernsey", "fra": "Guernesey", "hrv": "Guernsey", "ita": "Guernsey", "jpn": "ガーンジー", "nld": "Guernsey", "por": "Guernsey", "rus": "Гернси", "spa": "Guernsey", "svk": "Guernsey", "fin": "Guernsey", "zho": "根西岛", "isr": "גרנסי"}}, "GN": {"currency": "GNF", "callingCode": "224", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABhQTFRF/6UApNsA/wAA/wEA//8AAZkAAJkA////MYVkGwAAAAJ0Uk5T/v6mqd4QAAAAAWJLR0QHFmGI6wAAAAlwSFlzAAAASAAAAEgARslrPgAAABpJREFUKM9jUAIDAxcwEE0DA4ZRwVFBWgoCADuSl4cKLQ/UAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjQ3KzAyOjAw2Ns7QQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo0NyswMjowMKmGg/0AAAAASUVORK5CYII=", "name": {"common": "Guinea", "deu": "Guinea", "fra": "Guinée", "hrv": "Gvineja", "ita": "Guinea", "jpn": "ギニア", "nld": "Guinee", "por": "Guiné", "rus": "Гвинея", "spa": "Guinea", "svk": "Guinea", "fin": "Guinea", "zho": "几内亚", "isr": "גינאה"}}, "GW": {"currency": "XOF", "callingCode": "245", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAQtQTFRFfD8pfkAp6AAG9IIT//ge//Qe6QAG2QAGtAAF6wAGrQAEUAAC4AAG6gAGcgADEgAAvwAF3AAGNwABAAAAhAAD7AAG5AAG5QAG5wAGsgAFDgAAQwAC3QAG4gAGsAAFVgACSQACSgACLgABCwAAPwACRQACbwAD0wAF9YIT4QAGkgAEHgABAwAASwACwgAFRgACEwAAfAAD2gAGTAACBgAAkQAE6QAFrVogdqw4eKk3RAACAQAAiwAE6gAFGHhKG3ZJwAAFFQABGwABQAACCQAA4wAGHHpJH3hIiQAEAgAAOgAB3wAGGgABGAABxAAFVQACZAAD1AAFuQAFQgACjwAE2wAG1QAGtgAF////yn7lywAAAAJ0Uk5T/v6mqd4QAAAAAWJLR0RY7bXEjgAAAAlwSFlzAAAASAAAAEgARslrPgAAANdJREFUOMtjYEIDzCysWAHDMFTIxs7BRpRCTi5uHqIU8vLxC3ASoZBNUEhYRJSAQjFxcXEJSSlhaRkxMTFZcZwK2eTkFRQVlYSFlVVUVaXV1DVwmqippS0MBTq6enisZuMU0NcBqzMwlOHF6xlOI2OQOhNTXjYmM3MLS6wA7GtZK5BCaxugv20Z7OyxApBCXgdHYSdnF2EFV6BCRjd3rACkUNTD04vD28bH148Xv0JZ/4BATjbeoOAQTrwK2ULlXHlBfgoLl8BvIhMvNAmxEXAjMhhVSGOFALoCgvS7rw2DAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjE5KzAyOjAwL8YlRgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToxOSswMjowMF6bnfoAAAAASUVORK5CYII=", "name": {"common": "Guinea-Bissau", "deu": "Guinea-Bissau", "fra": "Guinée-Bissau", "hrv": "Gvineja Bisau", "ita": "Guinea-Bissau", "jpn": "ギニアビサウ", "nld": "Guinee-Bissau", "por": "Guiné-Bissau", "rus": "Гвинея-Бисау", "spa": "Guinea-Bisáu", "svk": "Guinea-Bissau", "fin": "Guinea-Bissau", "zho": "几内亚比绍", "isr": "גינאה ביסאו"}}, "GY": {"currency": "GYD", "callingCode": "592", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABjtJREFUWMPNmFtsFNcdxn9nZmevNtim2HFJcIydBtFQGwEhMYY0BRo1xsXQUqpGvRCemqohapW24CgO4lKpRE1JhVQU9alSlfQBoYQUkjawtolJWnBjcHAd2DU1a3C9F3uNZ3dm59aH9TolcX1NSv4v8zBnZn7nO9/5/jMj6h5bZ1VvKpN6nQuEYpcwrQySkBFC8Fko+Wbfv59LRQLiRw27qP3ieizbJqnF0EwVBwchpNsLeKDA3dTcdVmcaHuPjcur2bHhB6ws20KhvxQ1M8ywnsC0MgjEbVFVPlKS37TGr4gPbvRz+PXXmSufZP2yFKvKNvBQ5Xa+ULwaSbhIajFSxggO9ijo/wdW/nGRr6lAINYGFOwUHDoVIzF4jvsrX6NQOU9FQSlrK77DA+XbKM4rRzNUkloCw9IR8KmrKsIVRTa2LQTgAG+oBr8aSrPyETj4C4mFd8pAKfgfBn8DSWcxF6IhgqFjnIu8xcBIBNuxkYSE+BRUHQMckxTo0C32xNJ4q2wOPeti+VIBtgXCB+77IFCP5fkqkbSHs70tNIeP0T1wHjUzjBAS0ie4sT4GmIPsM21+GdP44A6Dg40KG9dJWX0cCxAgF4NvDQS2oIovcSneRzD0Gn/rfYPrwz2YtoEkybNWVd5Z5GvCcW65iwPMkQRr/C5iCXjhrQxun6BqiYRLlrKAjgp6J6jHcWeaWZDn4cHyzayt+D53z6vGckyS6TiamZpVXI0LmIP0CMEqvwu/LvFCs8HAiM39VTJeH+AIEFJ2pNUPWitCfZV8cZnF85fy5crtrCj7OnO9xaiZJDe1QUzbmHZcjbvEtwwYPZ5OGewfTLNsg+D5XS7K7hJgjTctC3CDcg8E6nD89cSNYs5fbycYOkrHjVYSqQHAQRLy7AHHpAYu6BZNsTSepTYvPutiRbU0DmSu7CywVAjeByHvGxhKDT3JEc5cPUlrz6uEE51oRiqbAP/DAlMGzEFeM232RtOESkyeb3RRv15GymXUuJVTVQGlEgJ14N9E0r6Tjv4LBENHOR85zYDaN25cyTuLfM/hOJPC5R41VxKsCSj0xR0OnTLw+gTVSyRcrolMMqqOHQOtDdRjeK2LlBWWU1vxPWoWbWPBnAp0M82wlkA302NNYFqAOUivENT4FYQu+HUwQ3wEapZJeDyTXT0K62hgdIP6Z6T0CQrcGkvv2sbD9+xg2YJa8jxziKrXUY2bzDhRPQIq3BKSKbja56Bnpj7JsZkigVAAFyDwKgHuyF/I5+eU43UFYPTMtEoCdAf+mNR5Sdf57naJxidkigrEBD7M1eiOkuaDrxbytoL3IYYyMh29bQTDe2mPNDOg9oGTzc5pAcpAzHL4TULjtDfDM7td7PiWjFth4k3iWCA84F4C/nrIayAjFhFK9HCm8whv9xynZ7Dr1h09mpVTBpSBbsNiX1QjcbfJ7xsVNqzJtb+J1PoceFdD/lYcz1oG0hbnQkGCob1c7H+HoXQ0O0zIyNLHcSYF/DCoTQ4k0pSvdnhlt5sl904U1B5QFkOgHgINqM5CumL/pCV8kHd73ySSDI99WkwW1hMC5vz28rDO71I6Dd8W7NmpMH/+R+FyoTwPvDWQtxXLvZrISJqzXX+hJfxzuqP/YERPjr3tjKfWtAAlIGE7HIprvKlkePppmR8+5sLrza3ef7e1eyGwEQKbGDJL6ejvIBjeQ/tHAniqUJMCysBlw2ZfNM2NBSZHdinUrZOyHcPOqVU0qtY3ybgeIJQc4sx7J7KGT1xCMz80vDyFnjslwJzfmtMm++NpSlc4vPyMm+r7AMsERxn1Vh2Or56BzDz+fu0czaGfcbH/7KSGnxWgAAwHXhnOcFjV+Npmwf6fypSW2OAUgj/b8FVpOV3xAVo6j/Luv04SGZ664WcMKAGDtsNvExrHhc5TTwqe3O4hMLcSfI9ieR/lWjqfd6600RJ+iu5o+4wMPyNAlxBcMSz2RdNcLTY5vHseWzbXIOVtZsiuoiN6jWD4D7RHTs3a8DMC/GvK4GBUpWjFIv50YBNVqx6he9hD6/utvH31JXri76N/QoafSYmKkhJn5bqv8JPdjxN19XHqynE6+9tuMfztLPFE4+P2wtp80Zk8Q0+8C+Mz9vPoP/IqxgbMRoXCAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjMwKzAyOjAwF7kM1gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozMCswMjowMGbktGoAAAAASUVORK5CYII=", "name": {"common": "Guyana", "deu": "Guyana", "fra": "Guyana", "hrv": "Gvajana", "ita": "Guyana", "jpn": "ガイアナ", "nld": "Guyana", "por": "Guiana", "rus": "Гайана", "spa": "Guyana", "svk": "Guyana", "fin": "Guayana", "zho": "圭亚那", "isr": "גיאנה"}}, "HT": {"currency": "HTG", "callingCode": "509", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFJChtpwYgEyx3Eix4wgAQwAAR////GpZHgQAAAAJ0Uk5T/v6mqd4QAAAAAWJLR0QGYWa4fQAAAAlwSFlzAAAASAAAAEgARslrPgAAACFJREFUKM9jUMICGEayoDEWwIAVCGIBDC5YAEMoFjCSBQF914N9eJyLmwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNTowOSswMjowMONsJdgAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MDkrMDI6MDCSMZ1kAAAAAElFTkSuQmCC", "name": {"common": "Haiti", "deu": "Haiti", "fra": "Haïti", "hrv": "Haiti", "ita": "Haiti", "jpn": "ハイチ", "nld": "Haïti", "por": "Haiti", "rus": "Гаити", "spa": "Haiti", "svk": "Haiti", "fin": "Haiti", "zho": "海地", "isr": "האיטי"}}, "HM": {"currency": "AUD", "flag": "data:image/png;base64,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", "name": {"common": "Heard Island and McDonald Islands", "deu": "Heard und die McDonaldinseln", "fra": "Îles Heard-et-MacDonald", "hrv": "Otok Heard i otočje McDonald", "ita": "Isole Heard e McDonald", "jpn": "ハード島とマクドナルド諸島", "nld": "Heard-en McDonaldeilanden", "por": "Ilha Heard e Ilhas McDonald", "rus": "Остров Херд и острова Макдональд", "spa": "Islas Heard y McDonald", "svk": "Heardov ostrov", "fin": "Heard ja McDonaldinsaaret", "zho": "赫德岛和麦当劳群岛", "isr": "איי הרד ומקדונלד"}}, "HN": {"currency": "HNL", "callingCode": "504", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAOdQTFRFCHvOCnzOAHbMM5LXM5LWMpLW/////f7//v7//v///P7++/3+q9Lu1Oj24O/5o87t/P3+qdLuCHXMP5XY4/D66/X7TZ7bAnHKm8rr1+n3TqDcfLnl1+r34vD5hr7nSp7bzeT18Pf8+v3+8vj8+fz+xuH0UqDc7/f83u348ff84O75HYXSPZXY9vr92uv4x+H0Tp/be7jl5fH66/T74/D53u759fr96vT7isDoRpravNzywd3yAHHKRpjZ/P7/VKDcAG7JtNXw5PD61en30eb20ef21un34e/52Or33+752+z4B3vOBnrOQDK1/wAAAAFiS0dEBmFmuH0AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAD1SURBVDjL7ZJZU8IwFIWD0HMT6lJEETGoVHBBRYWKsom7LP3/v8eGhmDLjMMbD/A9JGfunHtPMnMZW/MfiQVhGwvCklNSlpWMYVkpoxkMxBFDpGfaGG1sbgmtedhC2zuOHTMKQmY3uweaGCgd3IT93EEeXEQnEg4LR5hM4pBFpQjHJ6dh699olNyzcuirZM+lcrol52L+jRyXV1pUr2/KUIm3NfA5o7i7f6irukDDe2yqhqfnVtsxRgqx0en23OAXCiGC48VB//UNtjYwk/z+8Wnade3r+8dks4FG1opVOYggh97IlGbbMfb9+ML4/njZO7u6/AJNHh7awTqMIgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNToxMiswMjowMC3BcbwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MTIrMDI6MDBcnMkAAAAAAElFTkSuQmCC", "name": {"common": "Honduras", "deu": "Honduras", "fra": "Honduras", "hrv": "Honduras", "ita": "Honduras", "jpn": "ホンジュラス", "nld": "Honduras", "por": "Honduras", "rus": "Гондурас", "spa": "Honduras", "fin": "Honduras", "zho": "洪都拉斯", "isr": "הונדורס"}}, "HK": {"currency": "HKD", "callingCode": "852", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAadQTFRFugAAugICuwQEuwICuQEBtQAAuAMDuwMDuwEBtwAAvxkZ4Y6O5qGhvAcHuQAAugEBuAAAwiMj9+Hh////9dnZuwYG6rKy8c3Nz01NxCMj/vv7+Ofn8MfH12xssQAAtgQEtgAAvQ4OxSkpvQoKswAAyj8/9dvb+u7u4pOT3oWFuQYGxCUl+uvr9dzc++/v5Z6eyTs7+enp+u3t8MbGyjw867S0//7+//3978TEvRMT/PX19t7e3oqK5qWluggI5J2d+uzs7b299NfX6a2t9NXV/fb27b6+7sXFvyAg34eHxi0t1mlpyDMz67e389LSvQ0NzEdH/PT01GFhxSgoxi4u1F5eyTY29tzc5Z2dvhQUtAAAzEND+OXl9draxCYm7Li4/v3934iIuQICtwEByDQ02nd3yTg4wRwc7Le3xjQ03IeH+/Dw3H5+uwUFvAsL67a2/vr6/PPz0FJS9+Li13Jy+OTk0FFR9+Pj9NbW7sDA7bu72XZ2vAoKxCQkzUVF2XJyuAEBzUlJ+ejo8cvLwBgY0FNTzktLvxMTvhUV89DQy0FBuQMDvhYWXZk81wAAAAFiS0dEEwy7XJYAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFPSURBVDjL1ZPpOwJRFIfvdEsNmnt1LKMs16CsIWQZOyERyRpC9l32fd/5o/E8fZyZ+tr5/D6/85zffS9CSTmcjkuIw3pDijEBzsSnpqWbBRI3j2ZYIDMLaYMcIpjPhhwQebPOqM4KFHFCrtWWB/kFhQbGqZFCkSQUl5SCHRySWFYuVlCT8l5aWVXtrKmtc9U3NIK7CZpbqGIm5iVolaGt3dLRCV227h7opVgJJIj0efphYNA75BseAQ/4faPKLRnZGATsnvFgcGJyyjs9MxtSuwazOdk9vyCGpcWlZcoiOvW2V/Sra+sbm1vbO7DL9tQLJ2T/AA6jziO/1XGs9Yy60AmcuizyGZxfQFj55ljiJVwF4Pom6pDhVh3E/F0AIHr/wD8+wTPSEIPQl9e3dwOH2cfnF6+++Z9kTPizFn//UG3RSMwuEknsOyAU1/Cknl9sPTONe9hDGAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDoyOSswMjowME6LSZsAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MjkrMDI6MDA/1vEnAAAAAElFTkSuQmCC", "name": {"common": "Hong Kong", "deu": "Hongkong", "fra": "Hong Kong", "hrv": "Hong Kong", "ita": "Hong Kong", "jpn": "香港", "nld": "Hongkong", "por": "Hong Kong", "rus": "Гонконг", "spa": "Hong Kong", "svk": "Hongkong", "fin": "Hongkong", "isr": "הונג קונג (מחוז מנהלי מיוחד של סין)"}}, "HU": {"currency": "HUF", "callingCode": "36", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACdQTFRF1DYX1DUW0zIS10Il/PDu//////7+/v7+8ffuRpURNYsAOY4BOI0Atgdb1QAAAAFiS0dEBfhv6ccAAAAJcEhZcwAAAEgAAABIAEbJaz4AAABCSURBVCjPY2DABgSxAPoJYgVKWACDMRbA4IIFMIRiAQxpWAB2lRQKlmMB2FV2YAEMM7EAhlVYAMNuLIDhDBYwsIIArGfvEVFLwBQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzUrMDI6MDBFgSNxAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjM1KzAyOjAwNNybzQAAAABJRU5ErkJggg==", "name": {"common": "Hungary", "deu": "Ungarn", "fra": "Hongrie", "hrv": "Mađarska", "ita": "Ungheria", "jpn": "ハンガリー", "nld": "Hongarije", "por": "Hungria", "rus": "Венгрия", "spa": "Hungría", "svk": "Maďarsko", "fin": "Unkari", "zho": "匈牙利", "isr": "הונגריה"}}, "IS": {"currency": "ISK", "callingCode": "354", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAFFQTFRFADiXBDuYACuQVXi3////4W5v1R0dBz6aAC6SV3q4WHq4BC6SAB6JVXCz4W1vSnCzjaLL4m1u1R4e4m5u/vz85HBw5XV120BA1iQk1ykpjqLMkEWlzwAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAACQSURBVDjL7dLRCoQgEAXQUccprTR1K9v//9ANwgqURdinYs/LvFy4zChAwrhASSRRcAbf/IM/BVnStGoP6rZhOeBJ1w9oiAwOfcdzIBJlR3TeBxytEjnA0wsnomkbJSAPxs1LjMvsjCwAOnmK6xq3UVIfvFSHvTqUq6uXOfbX6Txvq0vnqT549RPe4T8+KfgBgwQOT3egtFcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzIrMDI6MDCAJh3/AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjMyKzAyOjAw8XulQwAAAABJRU5ErkJggg==", "name": {"common": "Iceland", "deu": "Island", "fra": "Islande", "hrv": "Island", "ita": "Islanda", "jpn": "アイスランド", "nld": "IJsland", "por": "Islândia", "rus": "Исландия", "spa": "Islandia", "svk": "Island", "fin": "Islanti", "zho": "冰岛", "isr": "איסלנד"}}, "IN": {"currency": "INR", "callingCode": "91", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAqZQTFRF53MA5HIC43ID53MB9XcA+3oA9ngA53QB83cA6XMCpV8oX0hPR0RkSUVlXkdPpF8o53EA428B9ncArmIkL0OAG02vZo3TfJ3agqLcZYzTJFSzLkJ/6X0R6X0S5XsS+IQOl2E+BDqjgJ7X5eny5enz5uv05er05OjyjancCD6llmE+/fXu+/Ps///zvMPUCDefqbre8fP32ODwyNPpwMzmwc3mxNDo1N3u8fL3v8znEj+kucHS/////v//NVuwf5bK9/n7vcrkma3WgZnMf5fLlKnUusjjzdfr+Pn7iZ/OOmCz/f7///7+/Pz9tcPgGUWh4efz2eDwvMrkf5nMXH2+aojDa4jEW3y9gJnMuMbi6u/3Jk+ms8Hf+/z9aofDXn2+9ff70Nrsm6/XXHy+jKPRxNDnxdHol6vVWXq8kqfTz9ns9vj7co3Ga4fD/P3+UHK5dZDI8fT5w8/ngpvNaIbDxM/nvMnku8jkcIzGfZfLws7m8/b6iaDQU3W6dI/H8vT6aIbCu8nkucfjydTpepXK9Pb6UnS67vL4ztfrmKzWWnu9kKbTmK3WW3u9kqjTy9Xq8PP5cY3GbIjD/v/+GEWh4ujz1t7ugZrNbInEfJbL7fH4JlCns8LgOVuxfpbKzNbqlqrUfpbLe5TKytTqj6XRPWCz/v7/8fju7vXt/v/zssXUCTigs8Hi8fT309zuxtHo0Nns7vL2wMznFECkr8LT///0QJkUQJoUPpcVSKIRKHVADDqki6DY4+ny4ujy4Oby4ejyl6ndEj+mJ3Q/LpAAL5AALI4BNZgAJHomD0iAJk6xd47Uip7bjqHceI7VLlW0LY4BM5IEM5MEMZEFNZcAMpMDIXUqDFZQDE9lDlBmDFVQMpIDNpgAOJsANpkAOZsAMZEGMpEFwJ5XlQAAAAFiS0dEPKdqYc8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGtSURBVDjLY2AY1oCRiRGICCtjZmFlY2NlYcZQyo4CGDk4ubh5ePn4OTkYUWUYBJCBoJCwiKiYuISklLSwkCCKFIMMAsjKySsoKimrqKqpq2toasnLySJJMmgjgI6unr6BoZGxiamZuYWllbWuDpIkgw0c2NrY2tk7mDk6Obu4url7eHp5A4XggMEHDnxt/PwDAoOCQ0LDwiMi3aOiY2x8EbJIJsbaxMUnJCYlp6SmpWdkZmXn5AKFECYimHk2+QWFRcUlpWXlxhWVVdU1tUAhHArr6k0jGooam5orWkxaa9pwKIy1yY1v7+js6k4t6unt658wcRKK1ZPhINbGb8rUaeXTw0NndM1sMp81e45NLEIWJXhs5s5zmF+2YOGivsjFDkuWLkMJnuUIsGLlqtVr1q5bXxZUumHjps1btq5AkmTYhgDbd+zctXvP3n1T9x/Ye/DQ4Z07tiNJMhxBAkePHT9x8tTpM2fPnT954viFo8hyDBeRwKVLl69cvXb9xs1b125fuXzpErIcw21UcPn2nbv37t2/A2SgAnSFQKUPHj58cBlDGFMhDjAUFAIALMfjyKVz+egAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzQrMDI6MDDj9ijFAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjM0KzAyOjAwkquQeQAAAABJRU5ErkJggg==", "name": {"common": "India", "deu": "Indien", "fra": "Inde", "hrv": "Indija", "ita": "India", "jpn": "インド", "nld": "India", "por": "Índia", "rus": "Индия", "spa": "India", "svk": "India", "fin": "Intia", "zho": "印度", "isr": "הודו"}}, "ID": {"currency": "IDR", "callingCode": "62", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAA9QTFRF5wAR5wAP6h8u/ODi////32xIVQAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAfSURBVCjPY2AYBahAEAtgUMICGIyxAAYXLGBUEA0AAKSuUllCEXddAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjQ4KzAyOjAwLpNLqAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo0OCswMjowMF/O8xQAAAAASUVORK5CYII=", "name": {"common": "Indonesia", "deu": "Indonesien", "fra": "Indonésie", "hrv": "Indonezija", "ita": "Indonesia", "jpn": "インドネシア", "nld": "Indonesië", "por": "Indonésia", "rus": "Индонезия", "spa": "Indonesia", "svk": "Indonézia", "fin": "Indonesia", "zho": "印度尼西亚", "isr": "אינדונזיה"}}, "IR": {"currency": "IRR", "callingCode": "98", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAnxQTFRFI59AJJ9BJaBBJKBBIJ49Hp08HJw6H508Hp07HZw7H509HZ07HZw6IJ09XbhyX7l0V7ZtVLRrYbp2X7l1XLhyVbVsW7dwU7RqXrlzYLp1Xrl0WLZuUrRpYLl1X7hzXbdxVrVsYrp3VLRqWbdvWrdwUrNpUbNoYbp1WbZvg8mTesWMm9OodsOIicuYfcaOhsqWltGkdcOHjs6dk9ChecWLjM2be8OKhMuWhNKcf8+Ym9Wqcb+Ce8WMiMuYks+gdMKGj86eldGjh8qXesWLisyZeMSKnNSpeMSJhMmUg8mU9fr27/jx+fz56/bu/P787vjx9vv39Pr17vfw7Pfv+fz6+Pz57ffv+vv57f32+PDu7dTO7NPN9vLu/Pz79fv38/r08Pjy+/387Pfu+v369Pr29vv47vjw+v378vnz/////vn5/Nja/ezt62Zn7Glr/e/w/NjZ/v/+/v///v7+7o2N6319/fDw6Wxs6Wtr6359//z89La26GZm7omJ6Who7oqK/vv77YSE75CQ6W1t6mxs75GR75KS8Z+f5ldX63Jy//7+8aCg//39+M3N6nBw75eX7Hx875aW+MzM9LCw+NDQ5U1N4TQ04S4u5U5O+NHR/fPz86mp3BAQ3A8P8qam/e/v/e7u/vj4/Ovr/vr6/O3t/fX1/vf3/Ozs++Hh+djY+dbW/fLy/vX1/fHx6GFh51xc6F5e5UtL6m9v51tb5lVV5lZW6m5u51hY6GNj6Wlp51lZ6WZm6GBg5lJS5UxM6GJi51pa5EhI5lBQ6GRk5U9P6WVl5lNT5EdH5lFR5EZG511d5UlJ2gIC2gAA2gQE2gEB2gUF2QEB2QAA2gMDJB6gxgAAAAFiS0dEabxrxLQAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAIBSURBVDjLY2AYkYCRkYmRkZGZGUYwAQlmJmQeWAEjAwsrGys7CwcnKxcXJzcrCzuIx8oDJDhBQjwcECFedgY+fgFBIWERUTEhfjFxCUkpaRlZOXkFcUV+ESVlIX4VVQlJCWk1SXUNBk0tbR1dPX0DQyMdY10TUx1tM3MLSytrUxtbO3sHQ0cnZxdXN3cPT2cGL28fXz//gMAgv+AQ78DQsPCIyKjomIjYoLj4hMSkZO+U1LR03/SMDIZMNJCVnZObl1+QhS7OUFiYCYJFYKqwOLOktKy8oqyyJLMYIlMIlUE1sSqzuqY2s6Ius76mGshBMRGF15DZ2NSc2VKX2dzaCOTgU9jW3pHZ2dLV0d2GV2FPZm9TX3/jhIl9TZOAHBSFXV2ZQNgFpXoyJ0+ZOm36jJmTM3tQZTCCp2rW7Dlz582vQhdnmLVg4aLFS5bOWrJ40cIFs5YtX7xi+cpVS1Y0LFm6en7Woqz5s9YsyVq0sGwtw7r1GzZu2rxuy9Zt23dMXbdz17qN23bv2DCjb/OevR1AoX37dx9Yd7Blz3aGQzMOH5mxd+/RjcemHthwcMu+/Xs37p1x+PiJQ1v2n9yy79SB41un7pixb8tphjNnz505f/7shYtnzp45dwnIu3z+/KUL54HgwiWYEEgBw1mQGJw4exaZdx6Jx3CWSDCsFAIAhAz8FIr+BqwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MjkrMDI6MDBOi0mbAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjI5KzAyOjAwP9bxJwAAAABJRU5ErkJggg==", "name": {"common": "Iran", "deu": "Iran", "fra": "Iran", "hrv": "Iran", "ita": "Iran", "jpn": "イラン・イスラム共和国", "nld": "Iran", "por": "Irão", "rus": "Иран", "spa": "Iran", "svk": "Irán", "fin": "Iran", "zho": "伊朗", "isr": "איראן"}}, "IQ": {"currency": "IQD", "callingCode": "964", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAhxQTFRF8wAA8wMD8gAA+6mp+qmp/////f////v7/vv7/fr6//z8/fr5/vz8/P38+/n4//3+7/Hu5+vn/vv6/v/+/v7+/v//8vb0nb6r8/f1+/381+TcvdPG/f7+6vHtydvR/f793OjhMndOg62UydzR0eHYzN3T9/r40+LZ3eji3+rj+vv6wdbK9Pj1r8m4UYhl6vHs/P39OntTvtTHssy81uTcU4pmrMe2+fv6udHDXZBvtcu7n7+shauRbZt9vdHD7/Xy2ubfPHpS7vTw6/Luapx+D14uBFYjMHZNwNXJ/f394+zmcp6Bcp+CyNnOM3dPBVckDl4tZJl6fqqQOndOW5JxvNPFlbeilLehZJZ3yNvQhq+XGWY3A1UiH2o9ocGu8vbzHmIzAEIJlrWe+vz7+/z7gKuSSIZhxtnNlLagc6GFytvQnbqlAEMKGl8v7PLtzdzSKmk8V5BvYpd4xNjMmLmjpsKvaJt80uHXSH5YAD4EY5Fw+/z8ydvQf6qQudDCgKuRdKOHU41rlLiiBVknr8q6eaSI0eDXhK2VtM2+ha6WwNXI5+/qosKvRH5X3ejhP3xUgqmQa56A2OXdZpZ23eni5+/rcqKFxdnN9vn39Pj2Z5t8P4BZz9/W4Ovk9Pf14+zn8/f0o8KwP4Bam7yots7A0uHY5O3n7/Tx+/v7+vr6/Pz8/Pv7+fr5+fr6+Pn59/n4/Pv8qampAAAAAwMDi7pPdgAAAAFiS0dEBfhv6ccAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGcSURBVDjL5dDnUxNBGMfxVfmagifibUiixhOEqLEFW6KABUEJRbELdgEb2JWiVAvYsCIGFcGeKPZ/kL1zmNEXzNxr/czszvObfXZn5xHivzTBJjHRJjFpfCkpfwTBXyYzHuFwOF1uh8WdOkWb6na4XU4zOdOmpaeluqarQ5eKAl1K6bEuSTK8PqRJJT8zZjJLFWaDjtAJzLYyBnMys5AGcwPoGJLsnOC8+QsgtBAPYpF/8RKWqr6w1HKXLV9BgJWrIj6CUVavycsvWMu69RsKNxrCV1S8aXMJRkxSWkZ5BSVbtlZuy2D7Dnbu2r1nbxXV+/YfOIjg0OEjR6mppe7Y8ROcPAX1DafPcPbceS5cvHS5sQmaW65wVdDa1t6hft7Zde36DW52Q8+t23e4e6/3/oOHjx4/6eNp/7P+uHpxoAZfjOcvXkYHXzHkxxjGP0CY12/eZr3jvSQYJB5RjYS1GB+8HzF0/feUtLC5maVHWuPQDDXwhCVGbTKUTHwKJcZ8Hvny9dvI96RVqyWwSfywSfy0Sfyyyf6L/1LjKG2PV8Pr5h+8AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjE1KzAyOjAw6GZPMgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToxNSswMjowMJk7944AAAAASUVORK5CYII=", "name": {"common": "Iraq", "deu": "Irak", "fra": "Irak", "hrv": "Irak", "ita": "Iraq", "jpn": "イラク", "nld": "Irak", "por": "Iraque", "rus": "Ирак", "spa": "Irak", "svk": "Irak", "fin": "Irak", "zho": "伊拉克", "isr": "עיראק"}}, "IE": {"currency": "EUR", "callingCode": "353", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRFMZQANJUDJY4Autup/////P37//3898uv5loC52UT52MQGbSutQAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAeSURBVCjPY2AAAUZlVxcgSKmYtQoEGEYFRwVpKQgAg3q7n/McyNsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzArMDI6MDAXuQzWAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjMwKzAyOjAwZuS0agAAAABJRU5ErkJggg==", "name": {"common": "Ireland", "deu": "Irland", "fra": "Irlande", "hrv": "Irska", "ita": "Irlanda", "jpn": "アイルランド", "nld": "Ierland", "por": "Irlanda", "rus": "Ирландия", "spa": "Irlanda", "svk": "Írsko", "fin": "Irlanti", "zho": "爱尔兰", "isr": "אירלנד"}}, "IM": {"currency": "GBP", "callingCode": "44", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABMdJREFUSMfN11tsVEUYB/D/N3P22gvdLbaUBiiEAqbcqlwCKCVGfYCgxgAGjFFDEDDyRmwsJmh88YnLkxoTUB64tCposBFiwKoQmoBE0srNXijd3rbtXtrt7p5zZj4fKhrolu4WDH7PZ+Y333dmvjOHTuPRhHhE7v8QZqJRRggphJACqR7gB4QJMACIf6dmIjIMQwjSOsIUVxAjFAIcAiQlC8FEkBKjrR6Qr6VSbSlD+fmeWIykZEBIKZlNIKJ1YkmF54MN4RK/oyFoWPG7RgEhQGp2MxMIrMXoNUgBCyDJnNi3t7/nj+zb/U4hElpH8/Jo48bC93dN27rNndcy9aUdLed/z25tYinBDAEwknORs8cTppfDQwMD+Tq57FmllCMUolR5p4BZCCdzKHAz+6OVg+GpZkeb562ti48effLVTaavONh1qrNx/+PzdruLfYHDR7Lu5MSguB8RM2vKO5/nbd0Wndma8E/W3TqrrYWEAN+buZGiClqzlIW/Xe88NLf8s0+dZhJZnraOjlvXr7dduGDXfOXbvMNhJMrWrP2zokLV1UFKKEWC8q7xbWO6f7f/RmWls/rEBLLdzABY65FIKhjQWruA3J/rexvqW/fu9d1s18mEo68v27IspXzbt0dig7le76RNG3vr6rKJFGAwh4Diqp12NKqPHy+AZQmDWSOVOuquBjMRzEBABdZPnFznaW56rCuQm0h4pBSA3RsUDheAaavXxr1ZDtuGy6WYZUVF8apnnIODsCybSCt7NBX3OU42YyJ4qMo2vpEGCZtJAYq1E2hvPBsOXzOHdDRebT03IwgSyraBxFPLS4qKrGCQABJi1JN0v4zv8BO62RtRgjU0kxCw7PCMWbO2bEn2VF+5tCLY2rnm8MU5p38YWrQ4AvItXNYXjbSdO+cCNN3fHatlaoImAGAhpNY9RUVLar97YuW6gkmVsWTeomXvxnu6fQsXLj59xvXxTh3bPzRoJ3866xplQ2UAgwEGEzm17jWM+TU102fPvtF4xVdYAMeCgXiwuGRKod+f73Yt3fy2xzdBJa+yhTGSBZDyHI8MQ8oQs/e9qhVvvN7W1l5UWtry46kbb37Y8XVtV6AzYVnCZZhxw1O8vOHsHjpyyZuMj9m0aczvMUmplIq/sqFs3/5oSx1Z3r4DJ/q/PFBApBgJsAkoIuUwjHkLuL07r/s2iEZ2jHuTSSNhZiDk7orHTkZ6azrrDVldP0nAJEHMHiG9zFCKTEtduiiIVBpqWqVmwAD0rXBy6qrpK6sK5z8fXDAn8cuvWbGYBsCaNfOwJwT/vc6H8Y4JYCDLsge+P9nZH5y1fl1ZeXl7Tk6stjZbSICZAAalTaYLD4cGsh0O6/LlQHNzyerVonRmd+st1dgYLpTewQy8fyLdqw8xm5aVYxiOY8dqy8vthobSyl3NS2aUHFKRYhLDlfkv4OGZbdt2GkZBU1PjCy9q03z68JmbX5TJLkaK+8gYkc6uvsvWti2J3MlkIHg1evAT/5FGjyANndE8GcMAIIg1J0t90ehx98FvcwhJ5gzLPC6YSNhQaukax/lcn4IpJSmVuZtG50oxBhjw+VyxQZdpZVzicWcMgIHcUIiJxq1i3H8SiogzaRcPDaYHUwH8BdRgRJpEqsB3AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjUxKzAyOjAwd6EO5QAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo1MSswMjowMAb8tlkAAAAASUVORK5CYII=", "name": {"common": "Isle of Man", "deu": "Insel Man", "fra": "Île de Man", "hrv": "Otok Man", "ita": "Isola di Man", "jpn": "マン島", "nld": "Isle of Man", "por": "Ilha de Man", "rus": "Остров Мэн", "spa": "Isla de Man", "svk": "Man", "fin": "Mansaari", "zho": "马恩岛", "isr": "האי מאן"}}, "IL": {"currency": "ILS", "callingCode": "972", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAUdQTFRF/Pz+////y8v1AADLBATNAADJQkLZQUHZR0fa5+f6/f3//f3++/v+GxvPzc31+vr+/v7/b2/hRUXZXFzexcXzPj7YYGDfoaHsenrkMjLWS0vbTU3bGBjQNDTWXFzfRETZFBTPT0/cTk7cRkbaTEzb9/f9x8f0JSXS9vb+i4vnUVHb+fn+3Nz4aGjgYmLeMTHUfn7kamrgfX3kT0/bzs716Oj6Q0PYkJDoNzfV9PT9ERHOeHjjoqLsAADIxMTz7e37CwvNd3fjnJzrYGDef3/lSUnZysr03t74NDTVkpLpOzvWvb3yIyPRg4PlRETY8PD81tb39fX9T0/aXl7dNjbVeHjkOzvYUVHcVVXdHR3RJyfTXV3fFhbOV1feUlLc0dH2NTXWUFDbe3vkJyfSWVnd7e38JCTRzMz17u78WVneWFjeBgbNtLTwwIVfVwAAAAFiS0dEAf8CLd4AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEhSURBVDjLjZRXVwMhEIXnWlJGdDEae40tutbYNbH33o3G3hP9/8/yCsvm8L1wuNwzMAwDkStwhCocoUpHqMoRqnaEIgGisWhQjNiyjrMta9aoYYF4bR08NjEiShDqEw1gKaURUbMJSWhMoqlZ6cyhRkJLa1t7R2dXd09vqq9/ADLEyBgcSg+P+KNibBwTk/puxmQqMz2jhtm5+QXWAhpnxOLSMjwpkM2t6EumcXVtXRk9bJQ3Eja3tncgsLu3f1Bma8LhUfr45PTs/OISV5nwZBjXN7f5u/tC8uHxKZX3Q68HkpX5+QWvWaUbpdFPrAon8Pb+AZJGYUDCwMPn1zd8Uxa2Z/YjLSIVA5QKpaBYtLVCztoKv464N9efI+5finDkH3lxVt+dR1YsAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjMzKzAyOjAwJlEWSwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozMyswMjowMFcMrvcAAAAASUVORK5CYII=", "name": {"common": "Israel", "deu": "Israel", "fra": "Israël", "hrv": "Izrael", "ita": "Israele", "jpn": "イスラエル", "nld": "Israël", "por": "Israel", "rus": "Израиль", "spa": "Israel", "svk": "Izrael", "fin": "Israel", "zho": "以色列", "isr": "ישראל"}}, "IT": {"currency": "EUR", "callingCode": "39", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFAFcAAVgBpcOl/////qSk/AEB/AAA2/OHLwAAAAFiS0dEAxEMTPIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAaSURBVCjPY2AAAyFjMHBNAwOGUcFRQVoKAgDqd3XHPs6NlAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo0MyswMjowMCyUH1IAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NDMrMDI6MDBdyafuAAAAAElFTkSuQmCC", "name": {"common": "Italy", "deu": "Italien", "fra": "Italie", "hrv": "Italija", "ita": "Italia", "jpn": "イタリア", "nld": "Italië", "por": "Itália", "rus": "Италия", "spa": "Italia", "svk": "Taliansko", "fin": "Italia", "zho": "意大利", "isr": "איטליה"}}, "CI": {"currency": "XOF", "callingCode": "225", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRF/96rqO6o/5oA/5sC////As0CAM0AN4JbVAAAAAJ0Uk5T/v6mqd4QAAAAAWJLR0QEj2jZUQAAAAlwSFlzAAAASAAAAEgARslrPgAAABpJREFUKM9jUAIDAxcwEE0DA4ZRwVFBWgoCADuSl4cKLQ/UAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjA4KzAyOjAwRRsubAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowOCswMjowMDRGltAAAAAASUVORK5CYII=", "name": {"common": "Ivory Coast", "deu": "Elfenbeinküste", "fra": "Côte d'Ivoire", "hrv": "Obala Bjelokosti", "ita": "Costa d'Avorio", "jpn": "コートジボワール", "nld": "Ivoorkust", "por": "Costa do Marfim", "rus": "Кот-д’Ивуар", "spa": "Costa de Marfil", "svk": "Pobržie Slonoviny", "fin": "Norsunluurannikko", "zho": "科特迪瓦", "isr": "חוף השנהב"}}, "JM": {"currency": "JMD", "callingCode": "1876", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAANJQTFRF/ssA/8wAdbEAAJYAApgAA5oAAJkA/cwA/coA/swA/88AproADpoABJoA9swA/9QA/MgA/MsA/9AA3MYANaMAAJUAAZkANKMANywA164A/9sA+8kA/84A+s0AbK8AAAAADQsAqYcA/M0A/MwAqbsAAwIAAgIAa1UA+s4A+8sA28YAAQEABAMANCkA27IA/9oADgsAqIYAAJcA3cYALqIA+s8AbFUA8ssA/90A/c4A/8sAX0wA/80AYE0AqLsAbK4A28UAqocAOCwA2K8ADZoA////0PowCQAAAAFiS0dERY6zqFcAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFkSURBVDjLjdRnk4IwEAbgRfQgKip4NlDXhhUVe+/+/990wXKERO/kW4Z35pnsbhZAkqSQHI58vfkiYTlEIwCKKpFoLC5rr3OaHI9FiaQqkEimiG6kvzNaVoxltUwubegklUxAvmBaoBdL5Rc8Zculog6WWcgDVqpmTVXqRkPgKdsw6opaM6sVBLuJrTblO0Y3F+A9tmt0KNtuYdMG7DnYHwwF/pcdDvro9BAQ0UF3xPM+O3JpAG9BFHiOxWeQ53nWDwb58Zhj2SDDT6bTyY2dzR9sIMjwiwXHBoMeX6kuV2uFEGW9WtIiO8xPNoi2jRtrC4TA1tp4pz+Cu2dw90eQ3pyl3Tf0p5eh5ZnPPiiPx94LvvcKvn+MF19wpoWHewsPL1vos0d/KI5BHjiWGbMgDzzLDG6AB4FlnwLDA7onnmUe142vmycX4XwxBVbgzcv5uQBy/y6A+0q5vl8p18dK+XRJ/QCDP20F6i/G2gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDozNCswMjowMOP2KMUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MzQrMDI6MDCSq5B5AAAAAElFTkSuQmCC", "name": {"common": "Jamaica", "deu": "Jamaika", "fra": "Jamaïque", "hrv": "Jamajka", "ita": "Giamaica", "jpn": "ジャマイカ", "nld": "Jamaica", "por": "Jamaica", "rus": "Ямайка", "spa": "Jamaica", "svk": "Jamajka", "fin": "Jamaika", "zho": "牙买加", "isr": "ג׳מייקה"}}, "JP": {"currency": "JPY", "callingCode": "81", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAGlQTFRF//////7+//z8/vz8/PDw9cfH8a6u98/P4lpa1hIS0gAA0QAA41pa//396Hp60gEB0AAA0wEB1AMDzwAA0wQE1AQE0wAA41tb1AYG9svL0gMD0wIC4l1d/O/v1hcX9cjI8a+v6Ht799DQeS2ujAAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADRSURBVDjLzZPZDoMgEEUH0FrBBdz37f8/sqBJqynqPDW9LyTk5A7M3AH4IxHKGCW3GGXbyei1m8Yc9/FwHY2SS857+lwI7gfeBUlCiGLJVZIoLuMIwjOSQJBmuSq0VJ6lJZyADKK0WrEVrdIImI0LoW7aN6fJtqn1pc2wk3mxUy47myWBfuBqDyo+9JZnUhiPnCFHoJbKk0iOYCImS200iC6N/gy6PfiGf49wto/QPLNEhcLEbP7EbD6PmQnuEmzBLZe7iKNWAdDLtbri1vWHegEoyhjkyVR7IwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDoyNSswMjowMIkrI+8AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MjUrMDI6MDD4dptTAAAAAElFTkSuQmCC", "name": {"common": "Japan", "deu": "Japan", "fra": "Japon", "hrv": "Japan", "ita": "Giappone", "jpn": "日本", "nld": "Japan", "por": "Japão", "rus": "Япония", "spa": "Japón", "svk": "Japonsko", "fin": "Japani", "zho": "日本", "isr": "יפן"}}, "JE": {"currency": "GBP", "callingCode": "44", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAeNQTFRF//7+//////z8///+//78+/nr62d5++Hl//39//z9/f36///78OOI3QAe4Bcy8JOf/vr6//798uu08eWW8/Li8ed/8Od/8eSW8+u04BYy3xEt3QAd5DhQ98HI+/jo9OND8dpI8sw088o08t5I8udE3gsn3gck4Bk03gkm3gYj6mR2++Xo//3836Fh4T8M4hsn2S8j3DAP65Bf98PK5kFY3AAc3xAs4Bcz3QEf8ZWh/vr7/v7/0GdZxYgX4IEj3Ise13sU31lU/v//8Zqm4R033QEe5TpR/vn81UVM218a3EQsz1sm1kUj2GJC/ff74Rw3/Ojr62l73ggl6mZ4/Obp//z/2FJSzGgY31sk2W8f2ksa5zZQ/f//98bN5T5U4Bs28Zej/vv722ty02UV3GAq1mwl1FMZ4Xlq/vr58Zml4Bw35TxT98XM8ai5zjoY2mUn12ok3BwZ8ae3/Ojq62h6/Ojp2oJj3EUT2UIT3H9h/Ofq98bM5T1U8p2n/fb76ktr6Utr/Pb78p2o//v84Bw25kRY9s7S9s7R3QYi3ggk3QQh3w8r+dLX5UFX4BUx3xIu3QUi8Zik+MvR62l63gcl8Zek/vv85TtS98LK5UFY4Bo18ZWi3gon/OXo5DlQ8JOg62h5r2l5ugAAAAFiS0dEAf8CLd4AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAJcSURBVDjLfZRnX9tADIdPNiMGYtyWDmxCZxJGd0gw6YZCdwOB7rK6HdJJugKdhBYIO+z9Uau7C8H2L+Ve2SedntNf0hECAgHTEnMgNxdyRPMeEYCQvHxwSKbNgkIoKoLCAtOW5ID8POKUixUQHOm9HbBzV8nuPSV79+EnXw4BlGLZSUpVrcyFwTlfhPL9Bw4eOnykHMQ0lYCrTFNLidvjraisqgZJYIajcOz4iZOnTuMHXYIE1VWVFV6Pm/hq/IFa3VsX5HzxzNlz5y9cvFQvcmqwzqvXBvw1Pky64XKjrGpNPs6/cvXa9Rs3b93mVF+TpsqNoQZMG4M3t4TD3rCntY3y79y9d//Bw0ePKbWt1YOGcEszGgg72N6hBWS9s6sbFHjy9NnzFy/hFXR3depyQOtoZy6EX0UwIjrye6IAr9+8fff+A0C0B6l6xBD45bkqkgS9nB/7WP/p85evEI9xai+1QcaRBe/rZ/xv33/8/PWbUfv7MgJDps7IHzASaPYP/vk76McjCWNgq2RgaghkxGNDw/JIMjkiDw/F4ptUuyPDjI65x1V13D02ukX9r+PExPaOm+jJZHJyGzRNZirBcp2eZvknprIkQzFRZo5gXbH+EXYkapeHCp6aoYKnqMLslwo+k7IIbikhC8EA9hKypuBFmZ3LXAoPz83y8qSbwtpmkkUEa5sJRohS53024XjjzlN+CPlkYZGOwtJy0CSFSbLg8hIdhcUFssKGa9VCtfBX2XCt0HFdc9mpVr5rjY6rU15XslAtfGUdH4AN25OSjY9PygaxP1LZ+UD+Ab9qjS2/1AnJAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjI1KzAyOjAwiSsj7wAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDoyNSswMjowMPh2m1MAAAAASUVORK5CYII=", "name": {"common": "Jersey", "deu": "Jersey", "fra": "Jersey", "hrv": "Jersey", "ita": "Isola di Jersey", "jpn": "ジャージー", "nld": "Jersey", "por": "Jersey", "rus": "Джерси", "spa": "Jersey", "svk": "Jersey", "fin": "Jersey", "zho": "泽西岛", "isr": "ג׳רסי"}}, "JO": {"currency": "JOD", "callingCode": "962", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAP9QTFRFwwAAWAAADAAAAAAA/wAA8wAAsgAATAAACgAA9QAAswAACwAATQEBHxUVHyIiHyAgHx8f4S4u4JSU4NfX4OTk4OHh4ODg/wwM/05O/7W1//b2/////wEB/wQE/woK/0xM/7Oz//T0/wsL/yYm/0FB/xYW/wIC/6Oj/9jY/1pa/0hI/7u7//7+/yIi/5+f/9ra/1xc/wkJ/0lJ/zEx/zw8//X1/wsM/01O/7S1//X29QYA4UAu4KaU4OnX4Pbk4PPh4PPg9AYAsy4ATWwBH5wWH6kiH6YgH6UfTGsACpMAAJsAAJkAAJgA8wcAsS8ATGwAwyQAWGQADJIAAJwAAJoAhntwuAAAAAFiS0dEGwJg1KQAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADhSURBVDjLjdBrWwFBGMbxKaONSEIHll2bXWcdHQopVFhC+v6fpd1rr23nZGb+r38vnvsB4OAwJBOA4SPlWApCGIkqJ1JQinpQgvpQSAMooCjkUhxyKAldGoufJs7IaOjQ5HkqnbnAY0EIL6+uszm8f6jmMVrQ9CITGjcl0+JRH5rlSpU8oFCrNyjYbN3e3ZPwgQHh45OJM0PT26wxlqpyGAKxOt3e8773IL30B8PX0RseDd/Hk+nH5xcZoNhsvrCXdIDBmAE5hkIuC6CA+VDIPCjBXLj6XouZAzfbn92v2Nl/FnWA7KnSQygAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MjMrMDI6MDAFOX3rAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjIzKzAyOjAwdGTFVwAAAABJRU5ErkJggg==", "name": {"common": "Jordan", "deu": "Jordanien", "fra": "Jordanie", "hrv": "Jordan", "ita": "Giordania", "jpn": "ヨルダン", "nld": "Jordanië", "por": "Jordânia", "rus": "Иордания", "spa": "Jordania", "svk": "Jordánsko", "fin": "Jordania", "zho": "约旦", "isr": "ירדן"}}, "KZ": {"currency": "KZT", "callingCode": "76", "flag": "data:image/png;base64,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", "name": {"common": "Kazakhstan", "deu": "Kasachstan", "fra": "Kazakhstan", "hrv": "Kazahstan", "ita": "Kazakistan", "jpn": "カザフスタン", "nld": "Kazachstan", "por": "Cazaquistão", "rus": "Казахстан", "spa": "Kazajistán", "svk": "Kazachstan", "fin": "Kazakstan", "zho": "哈萨克斯坦", "isr": "קזחסטן"}}, "KE": {"currency": "KES", "callingCode": "254", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAsRQTFRFAAAABAQEBQUFMDAwFBQUAgEBQ0NDDw8PPz8/hoaGCwsLHwkLcyIpNxATXFxch4eHAgICGBgYz8/Pf39/AgMDJAgLoktS02hxuFliPBAUQEBA4+PjYGBgmZmZz9DQLBwdlCgx3pWc2oaN4qSpqzZAJggLb3Bw0tLSJSUlAQEBGxsbYV9fhjY9wztH57u+68bJyU1XjikyXVVVKCgopKWloqWlnHx/vDpGxD9K68XJ2oWM79DTyVVfwDZCqXZ6oqSk+uXn9uPlwnd9wTRAwjxI57y/7czPyFFbwjM/xF1m7tna++XnyElUyUpVrUFKWx4jvDVBwjZC3Jmf5bS4xEJNxTdDgzM6ykpViSYuIAkLrDE7wzVCy15n0nF60nd/wjdDxDhEWxofRRMXwTZCxThEcCAnCgMDmSs1wjhExkhTPxIWKgwOuTRAwzdDYRshjCgw7cPD13yCwjVBMQ4RHQgKsTI9XRoghyYvxTZCzVtk+/Pz4qCluzVALg0QGAcIrjE8ZBwjiycwxTdExUBM35Waz2JqMg4RGggJsDI9dyIpCgMEmCs0wDdCQBIWJQoNtzQ/xDVBkCcwIwkMqzE7wzVBzGJr03R9ylpjWxogPRAUvjM/35ed4Jieyo2SazM3vDRA36Cm3I6U3Zqgwz9Kiigwh2Bj4pmf/////P7+y4iO6sPH3I2U6Lm9x05ZvUZR5dfZbbxtbb5ubJRUsztAxD9L3IyT68LGyVdgwzNBiVxAabdoAIcAAIYALZMtk2FOwjlF6cDD6bzAyVBbtD1DXoVHAokDAIkACYsJXKddKIAalUAx4Jqh4KGnwEFLUl0cUKFRKZUpAIgAPJs8Qp1CKm4NrWVd1nJ9yGJqZlUjBYQBGZAaXKZcBooGAokCPpk+DowOK3IQi0w1Vl0eB4QCAIoAQZtBHZAdAogCCIIDAocBBIkEA4kDbtigRwAAAAFiS0dEp8C3KwMAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGhSURBVDjLY2AYJIARic3EiEchMwuIZAXrYWPHo5CDk4uBgZuHF8jk4xfAo1BQSFhEVExcQpJBSloGnxsFZOXkFRSVlFVU1dQ10OQ0kQGDlraOrp6SvoGhkbGAJqqUCQowNTO3sLSytrG1s0eVMGFwQAGOTs4urkpu7h6eXt6oMgw+yMDXzz8gMEgpOCTUMCwcRcaHIRAZhEZERkXHxMbFJyQmJaPIBDLEI4OU1LT0lIxMi0DbrOycXBQpNIV5rPmhPgWFRQHFJaX4FCaUMZRXVFZVF9XU1tXjNbGBtbGpuaW1KKCtvQOvws6u7pQMl+T4nt6+fjSFzshgwsRJk6dMnTY9PmHGzFkoMs4Ms5HBnLnz5icvWLhocdOSpctQZGYzLEcBK1Y6u6xavWZt0br1qBLLGTaggI2bNm9x27pt+46du1AlNjDsRgF79u7bf2D1wUOHjxxFldjNcAwFHD9x8tTp1WfOnjt/4SKqDKrCi5cuH7ty9dr1Gzdv3b6DT+Hde/ePHXvw8NHjJxefPsOn8NhzEPHiJZB49RqvQtyA+goBOCjWcpsYznAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDArMDI6MDAdfAXPAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQwKzAyOjAwbCG9cwAAAABJRU5ErkJggg==", "name": {"common": "Kenya", "deu": "Kenia", "fra": "Kenya", "hrv": "Kenija", "ita": "Kenya", "jpn": "ケニア", "nld": "Kenia", "por": "Quénia", "rus": "Кения", "spa": "Kenia", "svk": "Keňa", "fin": "Kenia", "zho": "肯尼亚", "isr": "קניה"}}, "KI": {"currency": "AUD", "callingCode": "686", "flag": "data:image/png;base64,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", "name": {"common": "Kiribati", "deu": "Kiribati", "fra": "Kiribati", "hrv": "Kiribati", "ita": "Kiribati", "jpn": "キリバス", "nld": "Kiribati", "por": "Kiribati", "rus": "Кирибати", "spa": "Kiribati", "svk": "Kiribati", "fin": "Kiribati", "zho": "基里巴斯", "isr": "קיריבאטי"}}, "XK": {"currency": "EUR", "callingCode": "383", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA9VpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAxMy0xMS0wOVQxODoyMloiIHhtcDpNb2RpZnlEYXRlPSIyMDEzLTExLTE4VDIyOjUyOjIyIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDEzLTExLTE4VDIyOjUyOjIyIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2QzVGODQxRjQ3MTgxMUUzODQ1M0Y4QUE1NDU3M0Y2QyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2QzVGODQyMDQ3MTgxMUUzODQ1M0Y4QUE1NDU3M0Y2QyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZDNUY4NDFENDcxODExRTM4NDUzRjhBQTU0NTczRjZDIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjZDNUY4NDFFNDcxODExRTM4NDUzRjhBQTU0NTczRjZDIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+zU2ZVAAABG5JREFUeNrUV11oW1UcPx/33OQmDW3TpKZL2pSSWFen7UC2olOn6FYFB/NBBRWZDmH4ouxBxPkuOMRHnxybsDeRwZCxyajTzS/WsdrRtN2yNva7uU2W7/txzvHcZJ3NTJN+WfHPeTicc3//749zIX53COhZsMUkOyWQV4GR22rBtIDAf0T/K8GUA6pzSot7au0Z/zcFU1ZcFLS66Id9rt1BACF/LoyO7nN6HdTSoPjBpglG0DLRjtmRJ5SjzzoRNJ/pdB57refwk811BL73vP+TV7t3BRUZm8f2u954TCbIcoBAVSeMug8BZlS8E/in2sFHfZ7xuQzC6PPXw3u2ey8MxgcnC1BLfzuQiC7yVCYbm02e/j21MyAff+uRYCM5cy3Z4wMfv+iZUrNTKVhZA0yqCmb8ncfrD/V1jU+p5yNafDF5ORK/cltPatLFSGFChQyCMZX3j2k6Q3dyphpPfHddHZxlAvX2C13RyfgvtwyEYUXBEL/5w0p1zDnwu8y9IeV8JDefJ5xaR4hYjIgkPdSkR5NSOk9x0SiRXsywXAwRCLjMp0PKhSKqssXEUc1iCEGygAZjNMswRgCJhcUZYBo80pv5Yt9MirmuTjm4ThEB4lzcim/uQ60QYSLVSgEAbLA8tznA9j2BWQ9ZOPww3aZ4zo2Rn8ec2CEcAldCbbSOhVSMSW8YYq4tpoEPxz/oHv507+TuDnFF6FrKebWCudUrRJHKQY/yfndkl3tSN4ycTuNpGnLOv7Jjts3nstvWIFtapaFEklvd9o7GTE9DtMM2zZnBmKW0xoBJ+Uv+SAPJfTm0fWASMaYhCDdBMAdwxwP6y+1RWYJ9gYlGOadRlMlDsGSbCUCDTQ8rvAl5ncTNmS1vivzmG7cYuiS2vzXW6khlDUkzRWPhYBlfYd2dgtREsp/1/vjbQvM11XtiyJ83a5i9ihhzPjBXf2o0ZMeG6J3FIPJ/LkGQ0wNttw8GozIWH25CVotCNW+oDX8k3IqQzfgyAvd2jAEMWMFEJ0fCGZ3AWq6uLVh4DAL205/NJ0cfdNv1OmLyvzN9mcMht0t0QPWeG28xWO30WlU5CaYAGFfn3N9P+xHiwudWOwUlB3NoBZ03ygVxdGoklNBkhGpPx2ots0xBzJK67dJEYDzrfNSz6MCGySBGzCWbMqJiFN5M15+JBS/N+OIFIjSC96sOy3yAyWobiLBZpNZUxvb1UOflGZ+EuEvWFUL7pwNfjXTpHJ+40Xn8ys5Y2iEcUAFevZyEFqwY1NJounuISiNSgDmWdZq3Dy82HGwHIpynR8MXYy0EswHV/etMU44zrIuM4HgJAu5xK2ZDiX+FGLOlacj58ql8l8WSqmb/TEvKIGfH276JBBMmXtDI2VvbVE3Gsml5phxS4lZabK0ts8xlDPqUPOXQ4AhIDBfzSGwhWCOnKg+BFZ4lyOvQ/M78cLJO9M51Po+JQ1orRJTKfE6eTytQMhEE6yZpHRiMxBPH3OCDXgKKB0hb/tNGnH8JMADTwkmkucDL6QAAAABJRU5ErkJggg==", "name": {"common": "Kosovo", "deu": "Kosovo", "fra": "Kosovo", "hrv": "Kosovo", "ita": "Kosovo", "nld": "Kosovo", "por": "Kosovo", "rus": "Республика Косово", "spa": "Kosovo", "svk": "Kosovo", "fin": "Kosovo", "zho": "科索沃", "isr": "קוסובו"}}, "KW": {"currency": "KWD", "callingCode": "965", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAANVQTFRFAEcVALI1ANxCANpBANlBAAAAACIKAIkpANM/ANtCAAcCAFAYALg3ACEKAIgpALc3AE8YAIcpANI/ANlAACAJBIosG9lUH95YGyIdxd7N4vzq4ProHx8f4eHh////ICAg4uLiISEh4+PjIiIiIyMj5OTkKCAh5cvO/+Xn/uPmKAQIoBQj8TFG9TRJ9DRJCQECXgkT0RUp9hgw8xYv8xYunA8f7Rcv9Rgw8xgwXQkS0BUp9hgx9BgwJwQIXAkS0BQpJgQImw8fWwkSzxQp7BcvUAgQyBQnyHXz5QAAAAFiS0dEHnIKICsAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAC9SURBVDjLrczVEoJAFIBhjKOC3d2NsbbYYr3/I7nCwADDsufC//qbXxB8/kAQkQChcEREQZCiMZE/pRAgnuBPNUinSd5Uh4ipAUFKeU9NSKfpjIiC2jTLygoBcvlCkZEdQqlcqbrngLV6g5EDNtGwhYRtNOwgYffvsNdHwsFwNHbPDuXJdDZ3zwrJYrlabxhZoLzd7RVmJiSH4+ms8OHFc2dCcr157gx4Vzk7HZLHk7fT4EV9cXc/+P4gdrQvhL13AQCkBv4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTgrMDI6MDCJsS7yAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjE4KzAyOjAw+OyWTgAAAABJRU5ErkJggg==", "name": {"common": "Kuwait", "deu": "Kuwait", "fra": "Koweït", "hrv": "Kuvajt", "ita": "Kuwait", "jpn": "クウェート", "nld": "Koeweit", "por": "Kuwait", "rus": "Кувейт", "spa": "Kuwait", "svk": "Kuvajt", "fin": "Kuwait", "zho": "科威特", "isr": "כווית"}}, "KG": {"currency": "KGS", "callingCode": "996", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAmpQTFRFvgAnvgEnvQEovQEnvgYnwA4mxR0jwxckxyUixBwjwQ8lvQAnvgMnwhUlyzYf0U0c1Fga0U8b1l8Z01ca0VMbyjYgwhclvgInvwYmxRoj0Uwb2GcY2XAX3X8U4YgS5JQQ3oIU23IW2WkX0lAbxiYivwQnxBcj1Fca3HUV4YsS78IJ9dgG8swI8s0I9NUG8cgI5JYQ34MT1mAYxiAivwUnwQol0Ukc2m8W5ZgQ+OIE5psP1VoZ1VsZ0k0b45IR+OQE6KcO3nwUwxQkySwg23QV998F3XkU2m0W/fcB//8A/vwA3XsU+ekD5p0P23AWz0QdwQsl3XoU6KUOtgAszDYf7r0K12QYzToe5JgQ9NQH34ATwxYkvQAoxBck1l4Z45ER9+AF0lQb4IcT3noUxSQjuwApvAEoxCAk3nsU45QR0VIb9t0F4YoS1VwZxyMi2GUY5ZcQ8s8I/PQC12IYwA0mvwom1FQa+ecE8MYJ558P2WgXySoh4IQT0EYc2m4W0Ecc12MY12YY8MUJ5ZoP1l8Yyi0g1VgZ9toG1F4a8cgJ2W8X67MM78EK1mQY7r4K1WIZ5JUQxiEi0k4b3HgV56EOzDkf3HsV8ssI994F0VAc3oEUyjUg01Iawg8lzDcf4o4R+eYEyC4h8ckIz0gd1lwZ+OME5JMQ2WsXzDgevwIn1FUa3HcV5pwPzTse0k8b1VkZ9+EF56IO01Eb2GQY3n0U8ccJ1l0Zvwkn01Ya2WoX3n8U4IoS2GkXxyQhvwYnwhYlzDwf01ka23QW1FsazDofwxgkvwgmvQMnwRElxSEjyi4gxiIiwhMk////x9Cy4wAAAAFiS0dEzW3Qo0UAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGASURBVDjLY2AYkoARBIhRxsTMxExYJSMLKxs7BycXCzNepdw8vHz8AoJCwiKiYuIMuJVyS0hKScvIyskrKCopq6hy41QorqauoamlraOrp29gaGRsglOhqZm5haWVlLWNrZ29g6OQEy5/cDu7WLi6uXt4enp5m/v4+vnjUMcQYBaoExQcYh2qExYeHBEZpR4dw4jVwNi4+ITEpOSU1LT0jMys7JxcNm6sCmPy8gsKbTyLiktKy8o9wyoqq6pxmJhXbKdj41lTW2de3+DZ2NTc0sqNw+o2w/aOzi7h7h713r7+9gnFE7GbyBAwabLulKnThKfPmDlr9gRdjTlzuXFEzry6+QuqFoosslwsumTpsuUrVuIKcKdVq4Ms16xdtz7EYMPG1ZuiccYhS97mLXKLshdtnTF9guO2PB6cUci9nWPHzl12Oc27FfbE7S3FnSgYuPftP3AwLPdQ7uEjR4/F4EuRjOLHT5w0PnX6DIs4oUTOyMwMzApMRGUa4jLXIAQAnghpBAww9t8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDQrMDI6MDCCu0QYAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjA0KzAyOjAw8+b8pAAAAABJRU5ErkJggg==", "name": {"common": "Kyrgyzstan", "deu": "Kirgisistan", "fra": "Kirghizistan", "hrv": "Kirgistan", "ita": "Kirghizistan", "jpn": "キルギス", "nld": "Kirgizië", "por": "Quirguistão", "rus": "Киргизия", "spa": "Kirguizistán", "svk": "Kirgizsko", "fin": "Kirgisia", "zho": "吉尔吉斯斯坦", "isr": "קירגיזסטן"}}, "LA": {"currency": "LAK", "callingCode": "856", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAKhQTFRF6QAS5QEV+gAE+wAE/QAF/gAFaiF7biR8Zx96WxZ4VxN4AEHgAELgAEPgADXdBEriOXvtYZj0BDzPBD3PBTzPADTNb47i7e74///+/v7/AD3SAT7SADXQmbHt/////f3//P3/+/z+A0DTADDPbY7l+fv+/f7/ADvRDkbU4un6Az/TADLPSXTfBEDTAC/Pb5LmDkXUDUXUbY3lmLHtbo3i7O34BEniOHrtO5umIAAAAAFiS0dEHnIKICsAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADVSURBVDjL3ZPZDoIwEEXrgooiigKChQFkExRcWPz/PxOMJLSkSRPfPI8zJ2nmzhShf2LECRrTTKaCMJ0MymhGMV+Iy6W4mNN1tCKQ1vJmqyjbjbyWyA7a9dmrmn4wTNM46Jq6J1ro2AdbNjiu57kO2BYmWoR48gMII2iIQgj8E0vE8TmBj9eYkJxjzBDTyxVc+OLC9ZIyxCy/gdeJHtzy7FeR+2kc3/vDPJjDkPE82fG0gRdd4AUdOL3CsmpXWJWDFQ6Oon4pyqseHgX3mXEfLvdX+Cfe4LY7+JthuCEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTcrMDI6MDB/+V4bAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjE3KzAyOjAwDqTmpwAAAABJRU5ErkJggg==", "name": {"common": "Laos", "deu": "Laos", "fra": "Laos", "hrv": "Laos", "ita": "Laos", "jpn": "ラオス人民民主共和国", "nld": "Laos", "por": "Laos", "rus": "Лаос", "spa": "Laos", "svk": "Laos", "fin": "Laos", "zho": "老挝", "isr": "לאוס"}}, "LV": {"currency": "EUR", "callingCode": "371", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFqyMdqyQeqh8ZsTIs+fDw//////7+uszw1AAAAAFiS0dEBfhv6ccAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAySURBVCjPY2AYykAQC2BQwgIYjLEABhcsgCEUC2BIwwKwC2LVjtUirE7C6nis3hzKAACpijfJ8lhDKwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1OCswMjowMOI5SzYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTgrMDI6MDCTZPOKAAAAAElFTkSuQmCC", "name": {"common": "Latvia", "deu": "Lettland", "fra": "Lettonie", "hrv": "Latvija", "ita": "Lettonia", "jpn": "ラトビア", "nld": "Letland", "por": "Letónia", "rus": "Латвия", "spa": "Letonia", "svk": "Lotyšsko", "fin": "Latvia", "zho": "拉脱维亚", "isr": "לטביה"}}, "LB": {"currency": "LBP", "callingCode": "961", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAcVQTFRF/wAA/39//4CA/4GD3G5d4XBi////z+nSO5s+PJw/zOjP/P/91+rXLJAsAHYAAHcAGYUZWKdYstey+v368/nzbrNuHIgcIIogKI4oHYgdA3kDIosi0ObQ+/37r9WvMZIxYaxhSqBKK5ArbbJtttm2/f795fHlcbRxLZEtTqJOQpxCNJU0TKFMT6NPQJtALZAtnMuc3e3dkcWRcLRwUaRRDoAOEIEQCH0IKY8pK48rzeXNX6tfC38LBnsGKo4qR55HA3oDIYohRJ1ElMeU9/v3L5Eva7FrHYcdBXsFCX0JJ40nA3sDBHoEOJc4pM+kw9/DdbZ1KY4pFIMUHIccGYYZDH8MAHgANpU2c7VzXKlcJIwkP5o/x+HH/P78vNy8TaFNGIYYWqhaLpEuDYANUqRSZa5lmcqZ9fr1jMKMS6BLJo0mFoUWBXwFAHkACX4JT6JPOpg6I4wjG4cbqdKp6fTpgr6ClceV1OnUu9u74O7g9Pn06PPozuXOgb2BzOTM/v7+hL6E/v/+8vjy0OnTebx8c7l2ud68t927RKBIJ5ErRqFJut69+358yGNJ2mxb/H9+/4KD/4KE6HRp2GtY0GdRtVk1+Xx5fVEv1gAAAAFiS0dEBmFmuH0AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEJSURBVDjLY2AYBfgAIzpgYmZhZWbCEGZgwwDsHJxc3BiimAp5ePn4BQSFhAkpFBEVE5eQlJKWIaBQVk5eQVFJWkBZRRWvQjV1DU0tbR1dPX0DEbwKDY2MTUzNzMUkLCwVZPAoVLWytrG1s9dx4LdxdHJ2wa3QgNfVzd3D08vB29zH1w+PQv+AwCDX4JDQsPCIyKjoGFwKY+PiE0L47RMTk/h9kp1SUtNkcShMz9DKzMqWzHHIzcsvKCwqKMalkK2kVL2svMIsLEy8sqq6pja+Dqcb69nYXBpCPfk95YCcxiYXnArZ2JpbWtvaOzq7utGjFjWZMfX09vVPmDhp8pSp09BS2kBnh0EOAPCzPedbmSFsAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjA2KzAyOjAwFSRVMQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowNiswMjowMGR57Y0AAAAASUVORK5CYII=", "name": {"common": "Lebanon", "deu": "Libanon", "fra": "Liban", "hrv": "Libanon", "ita": "Libano", "jpn": "レバノン", "nld": "Libanon", "por": "Líbano", "rus": "Ливан", "spa": "Líbano", "svk": "Libanon", "fin": "Libanon", "zho": "黎巴嫩", "isr": "לבנון"}}, "LS": {"currency": "LSL", "callingCode": "266", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAUFQTFRFACCfASGfABydABmaES+mEi+mES+lEjCmIj+27vD47e/47/L67/H2lpihlpmh7/D2/////f39nJyaUFBOVlZUnZya/v7//f3+4ODhmpqaiIiJjIyMmJiZ5OTk/v7+lpaWU1NTTU1NlJSU5+fnUVFRREREl5eX+/v7mJiYAAAA+vr67OzsJSUlGhoa4+Pj/Pz8VlZWBQUFBAQER0dH8vLyampqAQEBAgICYWFh7e3t/v/++vv7eXp5HyAfTk9PJSYmKCkoISIiKioqSElIMTExb3BvnpydQD0/SEVHS0lKXVtcYF1fUU9QR0VGnZuc7vjy7ffx+//96PDsrrmzh5GLcnx2eIJ9iJKMsLu15/Dr7ffyEZxQEpxQDplNE55RH6pdI65hI65iIq1hHqldE55SAJNAAJM/AJE9AI88AZVEAJVDDeuuPAAAAAFiS0dEEJWyDSwAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAD3SURBVDjLY2AYkYCRSMDAhAmYmbEIMrCgAVY2dg4OdjZWdHEGTjTAxc3Dy8fPzYUuziCABgQFhIRFRIEUGmAQQwXiAhKSUtIysgLiaBLoJsoJyMorKCopAxloJqJbISAnr6KqhqEOU6G6gIampryAFiGFggLaOpqaunoC+gQVGmgaGmkaC8jJ4VMoJ2BiqmmmqWmuaWGJ5kwGKyQgZy1gY2tn7+Do5OziKqAuhyzHgGaxm7uHp5e3j4evH7qJ/iggIDAoOCQ0LDwiMjAKVYYhGgXExMbFJyQmJSanxMagyjCkooG09AwgSE9DF2fIJBIwZBEJhpVCAJSCyGJG8LK4AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTExLTA5VDE3OjI4OjAwKzAxOjAwP4Q+MQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMS0wOVQxNzoyODowMCswMTowME7Zho0AAAAASUVORK5CYII=", "name": {"common": "Lesotho", "deu": "Lesotho", "fra": "Lesotho", "hrv": "Lesoto", "ita": "Lesotho", "jpn": "レソト", "nld": "Lesotho", "por": "Lesoto", "rus": "Лесото", "spa": "Lesotho", "svk": "Lesotho", "fin": "Lesotho", "zho": "莱索托", "isr": "לסוטו"}}, "LR": {"currency": "LRD", "callingCode": "231", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAP9QTFRFAABmAABoTwFAzAEAzAAAAABlTwE/ywAAEhJwBgZpVR5d3E5OAgJnfn6xNTWFAABkAABjYV+e/vv7AABiHx9329vpgICyYWGg////OzuKbW2obW2nmJjB/f3+2NjodXWsc3OrYmKhGBh1WC5t5Hl5Hx94jIy67e30yMjeTk6VBgZqAQFm+fn8HBx3UAVDzgoKenqvwcHa3d3q4eHtJiZ8XUmI88HB8sHBEBBwn5/FaWmlDg5uKSl+m5vDX1+fDAxtHR13AQFlCQlqHh54AQFnXlCP9tLSAABnUQhH0BMTTwE+XmGhkmGI319f32Fh9tHR88XFzgwM5Hh4/vn52kdHTxH0QAAAAAFiS0dEGJtphR4AAAAJcEhZcwAAAEgAAABIAEbJaz4AAADiSURBVDjLjdHZVsIwEAbgKTStipIgIlUsUBQoiAuLFXBhX6qisrz/s5DanmNvBua7yNz8mZz/BCBEiURVTBBhflBTdUwQPDj8G0cxlJ87PolzOUTiFOU9K5JnqfOk4CDSBkpuu7jMXJnZXN4qsOsblNxYLJXlDdOuVBVN31WG3Vo1w7DvOCj3DyivCn+s1RvNFgP+5DjPCG9ju9N9eX1731cGoNcfMD4cjYFPpqj/f5HHbO6iQISwjx2t0yGfXwsUGET04DcR6ETgEsEPEb3MLxEsieitV0T0Mmsi2BDRy6hEWzLks2qStJ6PAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjM2KzAyOjAwdGk57AAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozNiswMjowMAU0gVAAAAAASUVORK5CYII=", "name": {"common": "Liberia", "deu": "Liberia", "fra": "Liberia", "hrv": "Liberija", "ita": "Liberia", "jpn": "リベリア", "nld": "Liberia", "por": "Libéria", "rus": "Либерия", "spa": "Liberia", "svk": "Libéria", "fin": "Liberia", "zho": "利比里亚", "isr": "ליבריה"}}, "LY": {"currency": "LYD", "callingCode": "218", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAI1QTFRF5wAT6gATcwAKcwAJAAAABQUFBwcHAQEBFRUVbm5ujIyMZ2dnNjY2CAgIFBQUq6uroqKiKSkpCwsLDg4OGxsbFhYWcHBwy8vLGhoaSUlJj4+PREREPj4+AwMDrq6ukpKSOTk5pKSk+vr61tbWMTExb29vjo6OQ0NDKioqbW1tNTU1EU8jI6BHI55G////DXIq7AAAAAFiS0dELlTTEIcAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB3SURBVDjLY2AYkYCRSMDAxEwcYGAhElCkkJWNnRiFHJxc3Dy8hBXy8QsICgmziYgSUigmLgGiJKWkZdjxKZSVkweRCopKyiosVFHIogq1Wk0dv9Ugz2gQ5RkWPk0ubi0igof4AMcBiFeoTSRg0CESMOgSCYaVQgBCOkYGjUbqbwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNTowNiswMjowMBUkVTEAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MDYrMDI6MDBkee2NAAAAAElFTkSuQmCC", "name": {"common": "Libya", "deu": "Libyen", "fra": "Libye", "hrv": "Libija", "ita": "Libia", "jpn": "リビア", "nld": "Libië", "por": "Líbia", "rus": "Ливия", "spa": "Libia", "svk": "Líbya", "fin": "Libya", "zho": "利比亚", "isr": "לוב"}}, "LI": {"currency": "CHF", "callingCode": "423", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAActQTFRFlXfplnjsl3jslXbpknTkkHLgj3HfkHLfkXPilHXnlnjrl3ntlXfq2h5iioX/i4b9lI/zqKPaj4r4iYT/m5brtrHKl5LvlZDzo57VoZzKnprYmpXvko3nlpKokYzxnprVoZzJop7Zko33k473paGWYmA8U1EyWFc7amhWV1VRPj0pXVtZZWNQVFI3T00vZmRBqaWpj4r7oZzlfXpRPDsePz4gPDsfMzMaGBcMMjEaFxcLNTQbOzocjIlmmpXxnZjtenhbMC8XU1IrUE8pPz4hXFoveXc+UVAqQUAhWFctUU8pMDAXjIl1l5L2l5Lgn5tzSkknp6RWsa9bkY9Lwb5jyMRnt7Rek5FMtrNelpRNRkUmpqKFkYztlZDmvLh4xsNlubZfq6lY9PB+3dpykpBL7el68e18o6BUvLhhvrthwr98k47sjIf/nZmnz8xp5eF2y8ho6eV44Nxzzcpp4d505uJ2w8Bk5eJ2x8Rjsa2ejYj+mZTqop5tZmQxRkQhNTQYLCsUKikTKioTMDAWPj0dVlQphoRJoZ3NiYb/iob/bWvIQUCALy9eJSVLICBBHh49Hx8/IiNGKipVODhwWFemhYH05Q9M5BBN////IjDadQAAAA50Uk5T/v7+/v7+/v7+/v7+/v7BShreAAAAAWJLR0SYdtEGPgAAAAlwSFlzAAAASAAAAEgARslrPgAAAOJJREFUOMtj4CMSMJCukJ9IhQKCQgQVCoOAiKgYiMKrUFxCUkpaRlZORF5BUQmPQmUVVTV1DU0tbR1dPX0DQ5wKhY2MTUzNzC0sraxNTG1s7YRxKrR3cHRydnF1c/fw9PL28cWpkM/PPyAwKDgkNCw8IjIqOga31bFx8QmJSckpqWnpGZlZ2bhNzMnNyy8oLCouKS0rr6iswu1r4eqa2rr6hsam5pbWtnZcIcnQ0dHR2dXd09vXP2HipMlTpnZ2YAcMIMDIxMzCysbOwcnFzcOAC/ASCRimEQkYphMJRhWOHIUA+L2cEmYnwtMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzArMDI6MDAXuQzWAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjMwKzAyOjAwZuS0agAAAABJRU5ErkJggg==", "name": {"common": "Liechtenstein", "deu": "Liechtenstein", "fra": "Liechtenstein", "hrv": "Lihtenštajn", "ita": "Liechtenstein", "jpn": "リヒテンシュタイン", "nld": "Liechtenstein", "por": "Liechtenstein", "rus": "Лихтенштейн", "spa": "Liechtenstein", "svk": "Lichtenštajnsko", "fin": "Liechenstein", "zho": "列支敦士登", "isr": "ליכטנשטיין"}}, "LT": {"currency": "EUR", "callingCode": "370", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAC1QTFRF/7MA/rMA/7UA5q0BC3YIAHIIAXMIAHMIAHQICG4IrQsBxAAAvgEAvwAA////KWO2egAAAAFiS0dEDm+9ME8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABCSURBVCjPY2AYfEAQC2BQwgIYjLEABhcsgCEUC2BIwwIYyrEASgWxWtSBBTDMxAIYVmEBDLuxAIYzWADDXSxgYAUBMVMEYE0sp5IAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDcrMDI6MDCzU16FAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjA3KzAyOjAwwg7mOQAAAABJRU5ErkJggg==", "name": {"common": "Lithuania", "deu": "Litauen", "fra": "Lituanie", "hrv": "Litva", "ita": "Lituania", "jpn": "リトアニア", "nld": "Litouwen", "por": "Lituânia", "rus": "Литва", "spa": "Lituania", "svk": "Litva", "fin": "Liettua", "zho": "立陶宛", "isr": "ליטא"}}, "LU": {"currency": "EUR", "callingCode": "352", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABhQTFRF/xkZ/9vb/wAA////7Pf/Lar/AJf/AJj/BnHklQAAAAJ0Uk5T/v6mqd4QAAAAAWJLR0QDEQxM8gAAAAlwSFlzAAAASAAAAEgARslrPgAAACtJREFUKM9jUMICGIaKIFYgiAUwGGMBg1HQBQtgCMUCGNKwAIZyLGAQCgIAGo+XaSlY34MAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDErMDI6MDC7Cw57AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQxKzAyOjAwyla2xwAAAABJRU5ErkJggg==", "name": {"common": "Luxembourg", "deu": "Luxemburg", "fra": "Luxembourg", "hrv": "Luksemburg", "ita": "Lussemburgo", "jpn": "ルクセンブルク", "nld": "Luxemburg", "por": "Luxemburgo", "rus": "Люксембург", "spa": "Luxemburgo", "svk": "Luxembursko", "fin": "Luxemburg", "zho": "卢森堡", "isr": "לוקסמבורג"}}, "MO": {"currency": "MOP", "callingCode": "853", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAj1QTFRFGZ0AGp0AF50AFJwAFZwAGJ0AIZ4AG50ADZwAjasDhqoDH54AkKsAMaAAFZ0BFp4CSp8AR58AFZ0CEZwBRKIAh6kAEJwAJZ4AHJ0AF5wDCJUAX75bWbtTCpYAGJ0DHp4AEpwAGZ4DDJgAW7lLldKL7vjs5/Xkm9WRUbVAGZ4CEZwAOKAAg6kAF54DIaAIDZgAk9GH6/fp+/78+f356fbnhMp2DJcAHaAIK6ADJJ4AQKIDDpkAD5gEApMAkNCF8vry/f/+/f796/brf8hyAZMADpkEF5oARaMDG54BEJoAGZ8SkdCFgcp0OKojWbhK+/77////5fTjTbM8OqslfshwfchwD5sJFZsAGp0BGJwAIKAIbcFcx+jC3fHa1+7UodeY4/ThyunGn9aW0uzO3PDZweW7Zb1THJ4DKqQT/P79+v37/v/+9vz2+f36/f7+2/DYF5wAG54DesZs3/Lc4vPg6Pbn5vXkdcRnFJsAGp4BKqQSntaUu+O15PTiueGypdibndWS+v363PHav+S5rNykhMt3HJ4EEZkAUbVCmNONl9OMl9OLZ75WdMRljs+Ci85/i81+hsx5gsp0QK4vEpoASLE1otiYn9aUpNialNGIV7dFXrtNn9aVnNWRpdmbmtSPOasjXrtOxea/xea+vuO3wOS5zerHzOnGv+S4w+W8zOnHweW6RrA1G54CMqgdgcl0qNqfp9qeqdqfqtuhoNeWdsVnIqEOD5kAFZsBM6gcfMdue8dtY71TMKcZFpwB1MAcYAAAAAFiS0dEUONuTLwAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFVSURBVDjLY2AY5oCRkQhFTEDMzMLAwMpEQCEbOzsHJxcHOzs3K36FPLx8/AKCQsIiomLs+FzHIC4hKSUtIysnL86KWyEjkwKDopKyiqqauoaSJqsWTnUK2jq6evoGhkbGJqZm5haiEricyWRpZW1ja2fv4Ojk7OLq5o4zlDw8vbx9fP38HQICg4JDQsPCI7Cri4iMio6JjYtPCAhITEpOSU1Lx2F1RHhGYIBjZpZxQHZObp5jQH5UAQ4jGQqLigOyswNKSgOyAwLKyisqcZlYVR1fU2tcV9+Q2tjU3NLahsvXHu0dBp1d3t09vX39EyZOmjzFgwGXkVOnTW+YMXPW7IY5c+fNX4DL10AjlRYuWrxk6bLlK1auWr1GaS3OOGRYq79uvfqGjZs2b9m6bTsedUCVOyp29uzavWfvlH141QHdGVGhDwQVlREMhEBEOhAQVjakAQCN/F7SFaUfOgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNTowOCswMjowMEUbLmwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MDgrMDI6MDA0RpbQAAAAAElFTkSuQmCC", "name": {"common": "Macau", "deu": "Macao", "fra": "Macao", "hrv": "Makao", "ita": "Macao", "jpn": "マカオ", "nld": "Macao", "por": "Macau", "rus": "Макао", "spa": "Macao", "fin": "Macao", "isr": "מקאו (מחוז מנהלי מיוחד של סין)"}}, "MK": {"currency": "MKD", "callingCode": "389", "flag": "data:image/png;base64,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", "name": {"common": "Macedonia", "deu": "Mazedonien", "fra": "Macédoine", "hrv": "Makedonija", "ita": "Macedonia", "jpn": "マケドニア旧ユーゴスラビア共和国", "nld": "Macedonië", "por": "Macedónia", "rus": "Республика Македония", "spa": "Macedonia", "svk": "Macedónsko", "fin": "Makedonia", "zho": "马其顿", "isr": "מקדוניה"}}, "MG": {"currency": "MGA", "callingCode": "261", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAD9QTFRF/3xr/3tr64hsb9J0W991W951//////7+/y8V/zMZ/y4V/zIZ4EIX4EUb/v/+G7giH7omAMwkAM0oAMskAMwoimvg7wAAAAZ0Uk5T/v7+/v7+v+O9ogAAAAFiS0dEBmFmuH0AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAA6SURBVDjL7cq3AQAgDAOw0Hvn/1tZyeALQLPIXhz5gNCPj0YRE8KjzAVhsarWER71mAiPZm3kx3fiAVAbNgcW0kZ2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjE0KzAyOjAwThFEhgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToxNCswMjowMD9M/DoAAAAASUVORK5CYII=", "name": {"common": "Madagascar", "deu": "Madagaskar", "fra": "Madagascar", "hrv": "Madagaskar", "ita": "Madagascar", "jpn": "マダガスカル", "nld": "Madagaskar", "por": "Madagáscar", "rus": "Мадагаскар", "spa": "Madagascar", "svk": "Madagaskar", "fin": "Madagaskar", "zho": "马达加斯加", "isr": "מדגסקר"}}, "MW": {"currency": "MWK", "callingCode": "265", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAZ5QTFRFAAAAAQAAAwAACQEAHQMBMQQCOQUCMgQCGQIBBgEAAgAAHQIBVwgDggsFkQ0Flw0Gkw0GlQ0Gnw4GjgwFeQoETwcDFgIBDgEBWwgDkw0Fmg0Glg0GhwwFfAsFdQoEeAoEgwwFgwsFlA0FkAwFSgYDBwEAfQsFoQ4GiwwFcQoEYQgESwYDNQUCKQQCKwQCOgUCTAcDZQkEdwoEjAwFDAEAFwIBjQwFmw0GYwkENgUCEQEBPgUCZwkEgAsFegsFdgoEGwIBvBAH1RII0xIIthAHbQkEEAEBJAMBXQgDpQ4GZgkEnA0GewsFVwcDGAIBWggD6hQJ/xYK/BYJ+BUJ4BMISAYDaAkELQQCCAEAigwFLAQCbAkE/xgK/RYK+xYJ/RYJ/xcKUQcDOAUCJwMBiAwFYAgEBAAAwREHuxAHvhAHvRAHuRAHxhEHow4GDwEBQgYCbgoEEwIBCgEADQEACwEADgEADwEAFQIBFAIB4xMH4RIH4hMH5RMI5BMH+BQI+RQI8xQI9BQI8xUI+BIH5hwLL384HYk8IoY7IYc7////Gt7rFwAAAAFiS0dEiRxhJswAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAE/SURBVDjLY2BAAoxMjAzMLKxsbOwcnAyMXAy4ABcjIzcPLx+/gKCQsIioGJCPXR0jg7iEpJS0jKycvIKikpCyiioDVpVcDBxq6hqaWto6unr6BoZGxkKaJthUMjKYmpmLWFhaMTEwAZGYtY2tuZ0JNnWc9kpqPA4MHHaOTs4urm4M7h6Knl6YRnIxqHj7+Pox+AcEBgUHBwWGhDK4h/HyhTPA/c4IBlwMEZHe9lEM0TGxcUHx8UFxCQmJDElGQnKgoAADuIHJKamWaSYu6RmZWUCQmZ2Ty5CXX6DoBrfcCgrcTAuL3AqLi0xKwMCkqLSwrKi8wg0mz1AJA1XVNbWVlbVwAGTXVFfXwqQZ6mCgvh5MIABMDAIYGogEDI1EgoFU2EQkYGgmEjC0EAkYWokEDG1EAoZ2IgFDB5FgZCoEANGsy4VJsnfVAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjUzKzAyOjAw4D4fzAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo1MyswMjowMJFjp3AAAAAASUVORK5CYII=", "name": {"common": "Malawi", "deu": "Malawi", "fra": "Malawi", "hrv": "Malavi", "ita": "Malawi", "jpn": "マラウイ", "nld": "Malawi", "por": "Malawi", "rus": "Малави", "spa": "Malawi", "svk": "Malawi", "fin": "Malawi", "zho": "马拉维", "isr": "מלאווי"}}, "MY": {"currency": "MYR", "callingCode": "60", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAbBQTFRFMgVt3AAK8FBV//7++srL6QwU6x4l/ODh/vLy7TU76AAI+K6x8m905wAA/vDxAAaJAAWKAQSKERaAFRl+BAqHAAWJAQaImAI07AAF6AAHDhKCbWlPfXdGQUFmERSBDhGCGBp9EhWABwuGmQs+7Q4U6Q4WAwmIiIFAl484EhaAAASKFxt9TUxgfHdHaWZRLC5xBwyFAgeJpIa1/87N+srMKixzyr8cLCxyAAKLPT5otKoo9OUG39IRcm5MAgiKp6nW////AAOLREVlxrofBw2FUVBd2c0U//IA+uwDkos7HiJ5AQaJnDdo81JV71BWLC5yysAcKSp0BQqHNTdsopoy5tgNzsIaZWJTExd/lwIx6wAA5wACBAmHjYY9k4w6DxOBEBSBPDxpYV5VUlFdICN4BQqGoGCQ+ZKT9ZCTAAaLAAWLDxSCcXBMeXdIOz9pDxWCBgyHDxODCQ6GBAmJAgiMp6nXNAVsMgRtMwRtRRVjSBdiOAlqMwRsMwVsNQZtsmCG+JKU3AAK2wAL5AAF6AAC8FBW6QwU6x4l/OHi/vPz7TU76AAI+K+x8m905wAA/vHx2P3g2gAAAA90Uk5T/fb29/b29vb39vb29vb3/hPHdAAAAAFiS0dEQP7ZXNgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEWSURBVDjLjdFFUwNBEAXgxp1eJMElOBskgeCW4Brc3Vlcl8VJcP/LzIELVbNV7zvNoevV6x5iZiUuPiFRYYt4WpOSU+RIzKWmpWdkZmXbcsRgbl6+nBgsKCwqVu0lpWXlDouzotIlR6xWVdfUOurqGxqb7Nzc4jZB7GltsyntHZ1d3T293Nc/4JUTiYNDw56R0bHxickptk7PzMqJjnPzC4vq0vLK6to6Ozc2t+RI07Z3dvf2Dw6Pjk807fTMtCPp+rlxcXllGMa1fnN7d2+WGOAT/H7fn4dHs46BT/94zVCQG0PBLgyFPGMo9AVDYa8YCn/DUMQ7hiI/MBT1icEPHv2FoZhvDJ4Id4S3hu8I/0zsDwZe5hctVdOZL0tejwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNTowNSswMjowMCTMT6wAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MDUrMDI6MDBVkfcQAAAAAElFTkSuQmCC", "name": {"common": "Malaysia", "deu": "Malaysia", "fra": "Malaisie", "hrv": "Malezija", "ita": "Malesia", "jpn": "マレーシア", "nld": "Maleisië", "por": "Malásia", "rus": "Малайзия", "spa": "Malasia", "svk": "Malajzia", "fin": "Malesia", "zho": "马来西亚", "isr": "מלזיה"}}, "MV": {"currency": "MVR", "callingCode": "960", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAblQTFRFtxQBtRYCuRIBvwwAvg0AuRIArh0GkjkVlDcUkzcUlDcVlTgWljgWljkXIahRKKBNJ6JOKqNQKqNRJaFMH55HG5xDFppAGpxDKKJOL5pKLptKMJtLMJxMJJZBIZQ/Np9RUatoa7d+hcSVdbyHPKFWLJpJL5tLLZxLMJ1NKZpIIpdBYLR2tdy/6PTr/v/+/v7+tNy/WbFxMZ5OJZhEOKFVvN/F/////f792+7gUK1pGpM6I5dDLJxKL51NJplFQaZc4fHl+v370OnXMp5PIpdCMZ5PL51MLpxM0+rZ+fz66fXsO6NXMJ5OH5U/j8qfdb6IHpU+K5tJM59R5PLo/P782+7hLJtKYbV4/f7++/38m9Cph8aY/P38/P79cLuEIJZAmM6nXbN0I5dCksuhXrN1MJ1Odb6Jcr2GSqpk+vz6n9KtxOPN+/374PDkLp1MYrV5e8GOHZQ9pdWy7vfwQqZdKZpHu9/E2O3eJ5lFms+o5fPpZLZ6T6xoq9e35vPq1Ovag8WVMZxNJpdEIJQ+NZ5QU6xqa7h/gsOTgMGQXLBxM51OLZpKLptLKaNQJaFNF5pAHp1GJqFNljgXHZ2dQAAAAAFiS0dENke/iNEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGBSURBVDjLY2AgBTASADB1TMwseAEzE0QdKxs7BycK4OLm4eXhgvE42NlYwfYys/PxCyABQSFhEVExcWEJKJ+fj50ZZDsjCwe/pBQcSMvIyskrKCopq6iqQUQk+TlYIAo5BaTUYUBDU0tbR1dP38DQCCYkJcCJqVDD2MTUzNzMwtLK2gafQls7ewczRzMnZxdXN3U8Ct1t3D3MPM28vI1d1dXxKfTx9TNzNDfzD3BWx6vQPTAo2CzELDRMXR2/QluXcLOISLOoACMCCn0Cos1iYs3i4jUIKkwwi9Q3S0yyJagwGaQwhaDC1Pg0s9hYs3SCVrvZZWSaRZplEfSMuptxtlmOWW4eoeBR13DJB9ldUEggwIEq44uAUVhckuRMQKG7emkZMFGUe/um+uThU6ieF1hRGatnVlXtW2hsg0+hep66b01tnblZfUONEYZClKyg1tjU3NLa1t7R2dXdg5oV0DNXr1CfiGh//4SJaJkLa3adxMvLzYGWXYkvAIgvUogCAHK1ht/GJTH9AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjA0KzAyOjAwgrtEGAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowNCswMjowMPPm/KQAAAAASUVORK5CYII=", "name": {"common": "Maldives", "deu": "Malediven", "fra": "Maldives", "hrv": "Maldivi", "ita": "Maldive", "jpn": "モルディブ", "nld": "Maldiven", "por": "Maldivas", "rus": "Мальдивы", "spa": "Maldivas", "svk": "Maldivy", "fin": "Malediivit", "zho": "马尔代夫", "isr": "האיים המלדיביים"}}, "ML": {"currency": "XOF", "callingCode": "223", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABhQTFRFq94AAJoAApsA//8A/6cA/wIA/wAA////biEhSgAAAAF0Uk5T/hrjB30AAAABYktHRAcWYYjrAAAACXBIWXMAAABIAAAASABGyWs+AAAAGklEQVQoz2MQBAMFYzBwTQMDhlHBUUFaCgIA/uiDX6UlVGoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NTUrMDI6MDCD7ir2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjU1KzAyOjAw8rOSSgAAAABJRU5ErkJggg==", "name": {"common": "Mali", "deu": "Mali", "fra": "Mali", "hrv": "Mali", "ita": "Mali", "jpn": "マリ", "nld": "Mali", "por": "Mali", "rus": "Мали", "spa": "Mali", "svk": "Mali", "fin": "Mali", "zho": "马里", "isr": "מאלי"}}, "MT": {"currency": "EUR", "callingCode": "356", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAXdQTFRF//////7+/O7u0RERzQAAzgEBzgAA/v////79//772LSTvKOFvqaMy6aC//Xx///+wKeLlJmcl56krJmC//Lp/v7/xKmMnJ2dn6KlsZyD/f////z6xKuTm52dnaCjsp+K/u/n/v7+//38//v3/vv3//36vqysoJ+fo6Ojo6Skrp6e+/Hu//76//v2t5+CvaSIo6KgoKCfoKCgnp+guqaRvaOGuaKIyqWB//bz/O3txauPlJqcnZ+hnJ6gpqams7OzsLCwpKSknJ+gsZyE//TtxqyQlZuenqCjnJ+ioKGhrKyrurq6tbW1oaGhnaChmJ+lsZ2F1rGQs5x/uaGGuaKKpKOhmpuboqKiqqmpnZ2dnJ6ft6SQuaCDtJ+Gx6J9//by//v5//jy/vjz//r1uqionp6eoKKiqpyc++/q//v1//jx//r3//r5xKyUsqCL/Ozl/f7/xKiMsZuCwKeMlJqemJ+mrJqD1bCOuJ+CuqOJyKN+//Xw//378+OKIgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAD3SURBVDjLY2BAB4wMTMwsrGzoAC7PDgREKeTg5OQgQiE7Axc3Dy8fUBUhhfwCgkLCIoQVivKLiUtIElII9IaUtIysnLwCuyI7Ox6FSsoqqmrqGuqaWto6utI4FSoycOnp8xiIGxoZGZuYmplbMFhiVyjKYGVtY2tkZ++g7mjrJOTsgluhq5u7h6eXt4+vuo2ffwBOhYoMgUHBIaFh4RGRUdExsXHxuBQyMCQkJiWnpGr4pqVnZGZl4wtHYPjk5MrK5eUz4A8eBn7GAv5CcYkiYIDzE4zC4pLSMiKikJ2hvKKyqpqwQgaGGl1daeomXKKzwqjCQaYQAKkSMZESNvARAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjM2KzAyOjAwdGk57AAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozNiswMjowMAU0gVAAAAAASUVORK5CYII=", "name": {"common": "Malta", "deu": "Malta", "fra": "Malte", "hrv": "Malta", "ita": "Malta", "jpn": "マルタ", "nld": "Malta", "por": "Malta", "rus": "Мальта", "spa": "Malta", "svk": "Malta", "fin": "Malta", "zho": "马耳他", "isr": "מלטה"}}, "MH": {"currency": "USD", "callingCode": "692", "flag": "data:image/png;base64,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", "name": {"common": "Marshall Islands", "deu": "Marshallinseln", "fra": "Îles Marshall", "hrv": "Maršalovi Otoci", "ita": "Isole Marshall", "jpn": "マーシャル諸島", "nld": "Marshalleilanden", "por": "Ilhas Marshall", "rus": "Маршалловы Острова", "spa": "Islas Marshall", "svk": "Marshallove ostrovy", "fin": "Marshallinsaaret", "zho": "马绍尔群岛", "isr": "איי מרשל"}}, "MQ": {"currency": "EUR", "callingCode": "596", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRFACZ/AymBABl3qbbU////+/z9//z8+7G68gok8xsz8xgwLe7hVwAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAeSURBVCjPY2AAAUZlVxcgSKmYtQoEGEYFRwVpKQgAg3q7n/McyNsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDkrMDI6MDCI5EAcAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQ5KzAyOjAw+bn4oAAAAABJRU5ErkJggg==", "name": {"common": "Martinique", "deu": "Martinique", "fra": "Martinique", "hrv": "Martinique", "ita": "Martinica", "jpn": "マルティニーク", "nld": "Martinique", "por": "Martinica", "rus": "Мартиника", "spa": "Martinica", "svk": "Martinik", "fin": "Martinique", "zho": "马提尼克", "isr": "מרטיניק"}}, "MR": {"currency": "MRO", "callingCode": "222", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAa1QTFRFLZxLLJxLLJtMLpxLMJ1KJppNV6s8aLI2IphPK5tMDZBWksInwtQWF5RTNp9IM55JNJ5JUak+KZpMjr8oytUT6OEI9ucD3t0MscwcN6BHKZtMPaJFTqg/J5pNmMUlOKBHq8se/+0A/+8AMp5JKptMHpZRgrwtaLM2IJdPL51KHJZRsM4cg7wsHJVRMZ1KMZ5KGZVScLUz+OgD5N8Kv9MXH5dQMp1J2t0NTahAJZlOLp1LHZZRhL0s8eYFnMUjXa46msQkRqVCG5VSnsYj7eQHsc0cHpZQI5hPHpdQ//AAKJpNztgS//EAksEnG5VRTadA7OQHarM1IZdPe7kv/+4A/+wAW6066+MH1tsPLJtL1dsPyNUUJJlOk8Em+uoC/OoBbbQ0I5lO++oB/OkB+ukCt84abbM0RKRDNJ9JO6FGWKw8lcIm5eAJ+ecCqcofdLYy/eoB++gB+OcC5N8J8eQF/+sA/uoA+ucCvdEXaLE27+QG/ekB/egB/+oA/+kA++cB/OgBpMchLp1KIZhPp8kf8+UE/u4AX645KZpNn8Yits4auM8ZqMkfIJdQ////k/cqxgAAAAFiS0dEjoIFs28AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFvSURBVDjLY2AYBTQAjEzM+KRhkiysbOwcDIy4FXJC1XNycfPw8vFDVDJyYigTEBSCmCksIiomLiHJCeQxS0nLyKI7g1OOjxNsoLyCohKPsgpInkVVTV1DE1Udi5a2jq4ekKFvYGhkbGLKAtZkZm5haYWm0NrGVk8FqJuZ1c5e1sERZKOyk7MLAyOa1ZqWFkqubizMDIwsmpaM7ixAvyh72Hu6go1GDR0vbx9fP00WBkZGTWZNRgZmZQ//AKXAIAyFLKbBIaF27pZ6LJrMzMyaeox+YeFKEVgDMjIqVDFaxjTIizNSKsZaLzYuXjEB00CgKxPZkpJDU1LT0jOUM7Oyc0JylfKwqQNZnh9aUBiiWCRaXFKqVFbuWSHEiD3SWUwrq5Sqy2tqa+vqGxRDG704m3BEN0uzdEtrSJuiYkhIdXS7KQMudUCVHR7S+Z1d3T06MjEcLPgSGzOLUK9br4aHh6WmJgN+wMwCBoSUDV4AACIxPKtk5SdLAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjAwKzAyOjAwdvRgCwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowMCswMjowMAep2LcAAAAASUVORK5CYII=", "name": {"common": "Mauritania", "deu": "Mauretanien", "fra": "Mauritanie", "hrv": "Mauritanija", "ita": "Mauritania", "jpn": "モーリタニア", "nld": "Mauritanië", "por": "Mauritânia", "rus": "Мавритания", "spa": "Mauritania", "svk": "Mauritánia", "fin": "Mauritania", "zho": "毛里塔尼亚", "isr": "מאוריטניה"}}, "MU": {"currency": "MUR", "callingCode": "230", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAADBQTFRF/wAA+wAEfwCAAAD/BAD7AQH+ERHu7u4R//8A/v4B+/0Af80AAJMABJwAAJoA////llr3LAAAAAFiS0dEDxi6ANkAAAAJcEhZcwAAAEgAAABIAEbJaz4AAABJSURBVCjPvcrRFIQAAADBVUghhRRSSCGFFFJIIYVTOIUUYkhgfms+9219ZQCfIzRBM/hkXMDnCm3QDh3gk/EEnz/oD13QDW/EB2rFAbh2CSgfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjA0KzAyOjAwgrtEGAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowNCswMjowMPPm/KQAAAAASUVORK5CYII=", "name": {"common": "Mauritius", "deu": "Mauritius", "fra": "Île Maurice", "hrv": "Mauricijus", "ita": "Mauritius", "jpn": "モーリシャス", "nld": "Mauritius", "por": "Maurício", "rus": "Маврикий", "spa": "Mauricio", "svk": "Maurícius", "fin": "Mauritius", "zho": "毛里求斯", "isr": "מאוריציוס"}}, "YT": {"currency": "EUR", "callingCode": "262", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFACZ/ASeApbLS////+6228xkx8xgw8anT+QAAAAFiS0dEAxEMTPIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAaSURBVCjPY2AAAyFjMHBNAwOGUcFRQVoKAgDqd3XHPs6NlAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDoyNiswMjowMLjDOXIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MjYrMDI6MDDJnoHOAAAAAElFTkSuQmCC", "name": {"common": "Mayotte", "deu": "Mayotte", "fra": "Mayotte", "hrv": "Mayotte", "ita": "Mayotte", "jpn": "マヨット", "nld": "Mayotte", "por": "Mayotte", "rus": "Майотта", "spa": "Mayotte", "svk": "Mayotte", "fin": "Mayotte", "zho": "马约特", "isr": "מאיוט"}}, "MX": {"currency": "MXN", "callingCode": "52", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAp1QTFRFC3ImD3QpAGgXn8ep////+/38/vv76a2tuAAAvQMDvAAA/v38/vz8/fz7/v39+/z7/f79/v79//7+///+1L+0q3RWu5aC6uHc/fr56q2tnsep8fTxz9vT18jEkF5Gi1IziDQHfSsBfzQMuZB7ncaozdzSYX1Ns384iUsagTQPlEQZjT8WhzsShy0AvYty6ayrn8iq9/n71de9bHU3p3pigjIHhDoThTkRiz4Viz0UhzMI6+rj7rW2twAAAGcVptCz69zalqCHiqyGu9K9mFI1gzMIgDgSfzcSgzAFjz4Siy4Au5yF77q8tgAADnQpAGcYmcGiiKeJYnNGv6illGpXhjcPiDcMkFo9wamciksphjIHvZyE87m8vQAAvAMDD3MrAGsMesKD6aq2sZ2PiaRzeVgoiGM0cS0HgDAGgDgQya2e39/ei1tBz6ecmsmYXS0AyAADugIADHQqCmUOp7mT6c/TPWwoja+DyqGL1q+Js3pml1xDeDESeyUAsnZYwLKl4MXEjsCMbCMAugEAC3QoDmofVZJM6vbub7R2LIYrWFMrfLOGhsSTwKqHmHAauZh6oXxjuXx12ebaUok+rAQAwAABuwEADXMqCXkQnLyN/+TthZN3ZKl1RI04E4QaI3oWHn8AVZhJG3sb4q+1o8qlJl8CvwQFAGoZor6jnK2DdrBwz8/ecbzuc4+oiEU5YDYLZ2Zajaipw7ymldOgdKxz4ZybuwICp8yx5/HmJ4snpa+FsK+mdIuUfTwsgzsyiHV3ncfAxMWuJYQgsua+9rS6tQAAAGkXnsao+vTzmbGKNJAxaaxibIpqd3VnbbBvHYkXs8it//X36Kyrn8eq+vz77fPs2OvdvLKntKac1+fb8vby/fv6/v7+/fv7/f38XwR2EQAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFNSURBVDjLY2BAAEYmZhZWFihgY2Hn4ORCAIbhrpCbh5ePoEJ+AUE+IaBiQYImCvOxiIiKibNIsEjiUSjFIi0jKyevoKikzIfXRBUWVTV1DU0tbR1dPRZ9PAoNWAyNjE1MzcwtLK2sbWxxKrSzd3B0cnZxdXP38PTy9vHFpdDPP4AlMCg4JDQsPCIyKjomNg6HwviExKTklNS09IzMrOyc3Lz8gkLsCouKS0rLyisqq6prauvqGxqbCpqxK2xpbWvv6Ozq7unt658wcdLkKVOnYVfIMH3GzFmz58ydN3/BwkWLlyxdxoXLM8tXrFy1es3ades3bNy0ecvW2G04g2f7jp27du/Zu2//gYOHDh85ijMcjx1nOXHy1OkzZ8+dv3CR5RKemLnMcoXl6rXrN27eYrmNN1EAk5kgJN3euUsw4d65c+/enTuDPXPRSSEA9OV48ySYOO4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDYrMDI6MDB+rDD1AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQ2KzAyOjAwD/GISQAAAABJRU5ErkJggg==", "name": {"common": "Mexico", "deu": "Mexiko", "fra": "Mexique", "hrv": "Meksiko", "ita": "Messico", "jpn": "メキシコ", "nld": "Mexico", "por": "México", "rus": "Мексика", "spa": "México", "svk": "Mexiko", "fin": "Meksiko", "zho": "墨西哥", "isr": "מקסיקו"}}, "FM": {"currency": "USD", "callingCode": "691", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAR1QTFRFZ5fWaJfWaJjWZZbVZpbWX5LUhazeg6rec5/ZXpHUwtXuvtLtZJXVnr3l6O/5+Pr95+/5nLvkY5XVW4/To8Dm////oL7laZjXvNHtuc/sZpfWaZnXgKjdgajdaZjWYpTVZZbWapnXjbHgdaDaWY3SYJLUd6LajLDgTobPqcToz97yr8jpsMnqi7Dg0uDzpMHmdKDZpMDm4er3+vv+iq/gYZPUjrLg/P3+3+n2or/mdaHaq8bo6fD5f6jdYZPVgqrdTYXPxtjwmLjjrsfpZZXVr8nqlrfjytvxtczrbZvYWY7Sb5zYY5TVe6XbXpHTfKXcvdLts8vrts3ru9DtX5HUn77l6/H6+vz+6vD5mrrkxdjvwtXvXZDTiK7fh63fGEKQlgAAAAFiS0dEFeXY+aMAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEzSURBVDjLY2CgADAyEqeOiZmZiRh1LKxs7KwsRNjLwcnFzclB2F4eXj5+fgFBIYK2MwmLiIqKCRN2pTinhAS3JKc4QYVS0jKcrLJyxHhbXk5OHq/jYMHMBEaQIMAS9IxCCopoPmBSZObBUCnHo6SsooqikklVRU1dCMO1Gppa6tpyUggBFkUdXT19DTR7FQwMjUSNTUyRjGQyNTMXtbDkUEBVaGVtI8pvK4+s0M7eWFRA00ABzZkOko5Ozi7IIgqubu4eDhiBo+Dm6SWP4nI5eRVvNxeMuGT0YcEMHhYfLAHJhMSABzjetOEjLufDQBhIKfr6cfqLSxFUKMcaEBgUHEI4+ch5hYqKCnoRVMjEIxgWbhwRSTgrMKjxREXzqBHhG+aY2LgYZiIUAiNJgagCgFIAACGpHL8Jj431AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjAyKzAyOjAw4WtxIgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowMiswMjowMJA2yZ4AAAAASUVORK5CYII=", "name": {"common": "Micronesia", "deu": "Mikronesien", "fra": "Micronésie", "hrv": "Mikronezija", "ita": "Micronesia", "jpn": "ミクロネシア連邦", "nld": "Micronesië", "por": "Micronésia", "rus": "Федеративные Штаты Микронезии", "spa": "Micronesia", "svk": "Mikronézia", "fin": "Mikronesia", "zho": "密克罗尼西亚", "isr": "מיקרונזיה"}}, "MD": {"currency": "MDL", "callingCode": "373", "flag": "data:image/png;base64,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", "name": {"common": "Moldova", "deu": "Moldawie", "fra": "Moldavie", "hrv": "Moldova", "ita": "Moldavia", "jpn": "モルドバ共和国", "nld": "Moldavië", "por": "Moldávia", "rus": "Молдавия", "spa": "Moldavia", "svk": "Moldavsko", "fin": "Moldova", "zho": "摩尔多瓦", "isr": "מולדובה"}}, "MC": {"currency": "EUR", "callingCode": "377", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRF8xgw8xkx8xQs9Cg+/u/x//////7+xfBoaAAAAAFiS0dEBfhv6ccAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAmSURBVCjPY2AYwUAQC2BQwgIYjLEABhcsgCEUC2BIwwKwqxwZggCotWuVLjeL7gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDozMCswMjowMBe5DNYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MzArMDI6MDBm5LRqAAAAAElFTkSuQmCC", "name": {"common": "Monaco", "deu": "Monaco", "fra": "Monaco", "hrv": "Monako", "ita": "Principato di Monaco", "jpn": "モナコ", "nld": "Monaco", "por": "Mónaco", "rus": "Монако", "spa": "Mónaco", "svk": "Monako", "fin": "Monaco", "zho": "摩纳哥", "isr": "מונקו"}}, "MN": {"currency": "MNT", "callingCode": "976", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAY9QTFRF1hUX0xca4w4LSGWcAJbsBIzcAY7fSWWc1hYX1hcX1RIX1AwY2SUV5VsP2ywU1REX1A0Y3TgT+8ID2y4U1Q8Y1RAY2ysU63oL3C8U1hQX9aQG/s8B+bcE2CAW1xkX1RMX74sJ97AF8ZQI3TUT1hMX1BgZ0wgZ2SEW41MQ52YN5FgP2igV0g8b4w8L4kwR6W0N2ioV5mAO7H0L5mIO3DIU6W8M3UYU4gkMSWec/98A4UcR2B0W6W4M9qoF6GoN1xsW5VoP/+QA6oQN4AIMSWic74kJ/tAB3jsS5V0P8pgI854H5V0O4kwQ/9QA6XsO4AMM/tEB4EUR0wgY52MO6nMM3TcT0wUZ/9UA6XwO3joT4UoR/c0B63gL7ocK2B4W41IQ/9YA3jkT4k4Q8pkH/MYC2y0U4k8Q/9cA/tIB4EQR0gQZ41UQ63YL0QAa5FcP4EMS52cN7ocJ7oMK5FUP8JAI/9kA4UkR7HsL7HwL/90A6oAN1A4Y+LME3j0S0wkY4lAQ0wYZ+LcE5WwQ4QQM////bDwefAAAAAFiS0dEhGLQWnEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAD/SURBVDjLY2BAAEYmZhZWNihgZWFnYmTADkYVDj6FHJwcRCnk4OLm4iBCIQcXDy8fPwdhhZwCgkLCIkQo5BAVE5cQJUIhg6SUtIwsFwcBhZxy8nwKikrKKnKqeBVycKuJqGtoamkLqOno4lPIr6dvIGdoZMxhYmpmboFHIbeSpZW1ja2dvYOjk7MLHoUCrm7uHp4KXt4+vn7++BRyKwQEBgWHhIZphUcQUOgbGRUdExsXn0BQYWJSckxKalo6IYUZmVnZOYa5eQSsFlBwyy8oVCjSLPYlYGJJaWZZuU2FVV5lFd7gqY6pqa2LV6hv0GtsasanEJy6q4GQgxNvogAAwKY43gNfgNYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NTkrMDI6MDBETkCCAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjU5KzAyOjAwNRP4PgAAAABJRU5ErkJggg==", "name": {"common": "Mongolia", "deu": "Mongolei", "fra": "Mongolie", "hrv": "Mongolija", "ita": "Mongolia", "jpn": "モンゴル", "nld": "Mongolië", "por": "Mongólia", "rus": "Монголия", "spa": "Mongolia", "svk": "Mongolsko", "fin": "Mongolia", "zho": "蒙古", "isr": "מונגוליה"}}, "ME": {"currency": "EUR", "callingCode": "382", "flag": "data:image/png;base64,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", "name": {"common": "Montenegro", "deu": "Montenegro", "fra": "Monténégro", "hrv": "Crna Gora", "ita": "Montenegro", "jpn": "モンテネグロ", "nld": "Montenegro", "por": "Montenegro", "rus": "Черногория", "spa": "Montenegro", "svk": "Čierna Hora", "fin": "Montenegro", "zho": "黑山", "isr": "מונטנגרו"}}, "MS": {"currency": "XCD", "callingCode": "1664", "flag": "data:image/png;base64,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", "name": {"common": "Montserrat", "deu": "Montserrat", "fra": "Montserrat", "hrv": "Montserrat", "ita": "Montserrat", "jpn": "モントセラト", "nld": "Montserrat", "por": "Montserrat", "rus": "Монтсеррат", "spa": "Montserrat", "svk": "Montserrat", "fin": "Montserrat", "zho": "蒙特塞拉特", "isr": "מונסראט"}}, "MA": {"currency": "MAD", "callingCode": "212", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAn9QTFRF6wAT6gAT7wAS7AAT5wIUzA0Y7gAT8gASzwsXUDgr6AET+gARnR0fAFQ30AsX6QET+AARWTUpAFo5jSMh+wAR5wEU2wcVHEoyAFY4Qz0t8wAS6QAT+QARnxwfGkszDk81zgwXUzcqC1A1kCIhjiIh/AAQ7QAT3wYVEE80OUAu6wITDlA1MkQw8AAS7gAS9wER/wAQpBoeAFo6gicj/wANTjgr6QQT9wAR6gET8QASqBgdTTkrWzMpXTMpWTQpUTcqQjwtXzMophsdUzYqUjcqVDYqSDosSzkrTDkrSTosVTYqvxAa6AEUeygkBFY4AFg5GUszHUoyBFI2figkH0kyOkAuhiUiAFg4BVI2GEszCFE1Ek00G0szqhcd7AETxA4ZMEMvAFc4Rzos0AoXnhwfeColzAsY2wYViyIiygwYuxEaLkQwXjIo5wQU9gAR6AMUXzEoAFk5GUwzuBIbthQb+wAQ7AITliAgAF47ohoeAVY3DlE1lx0g7gET7QETmxwfEFA1aS4nmR4f6AQT2wgVBlI2by4mJ0Yx0goXzQwYQD0tAFw6Oz8u6wET8wESRjwsCFE2whAZ9AAS5gEUxw8YbSwmA1c4bS0mNUIviSQinB8f5QIUJUcxbi0mqxcdRjsslB8gMUMwuxIaBFM2UTgq9QASbi8moxkeH0ozZS8n1wkW/wAPN0EvE0404gQUvREaaS4mAFs6E1A1phgdkiAhDk80kiEhsBYcFE00L0QwrxUcIUkyai8nDE81A1Q3CVM2hyMi5QEU5gQUWDQpA1M2MUMv/QAQfygjNkEvww4ZkCAhC1E1AVQ3xQ8ZMUQw9QARdysl0gkXrBYcjiEh////1mO7jgAAAAFiS0dE1Am7C4UAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAHMSURBVDjLY2CgBDASq5CJOJXMLKxsxKhkZOfgJMpILm4eXj52fiIUCggKCYuIEraZSUxcQlJKmrCBMrK8cvIKhO3mklFUUpZQUcVpNyMXGIiqqWtoamnr6IpC+BgGM+qpcsvIcMvoGxgaGZsYm5qpmgsImJtzm1ugqrOwtLK2sbWzt3dQceR1cnZxdXP38HS39/L08LZEMZSRwcfXz583IDAoOCQ0LDxCPlI+KjKaVygmFj3sGeMM4hMSk5INUoRSWdLSYzMydbOyJXJyuTHjSDQvv6CwqFhFqKS0zKC8QriyqrpGphab57nquOsbChubmhlF1VpaPdraO0S4sMc5I5dIZ1d3T7xAb596/4SJZZNwx+PkvClTp4WriEyfMVNoVulsPPHCPGfuvPkL5lcsXLR4iQAXTmX8Syf6LzNZvkJo5arVa9auw518RVVTJdZv2Lhp85atVtsKt+MxUi9hx87u+R0Gu3aHJ/HuwZko+SdN3Ltv/4FJ7AdlDh32P3K0F6fNx453nzipasHP7FMrc+r0mbOTcCRKZoZzK2vPi0KCVDU+5sIx7HYzWl68pBoH84CoQMtlZlx2q51HijMupj7cGQc16RGRZbEAAARidfCTzzjjAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjAyKzAyOjAw4WtxIgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowMiswMjowMJA2yZ4AAAAASUVORK5CYII=", "name": {"common": "Morocco", "deu": "Marokko", "fra": "Maroc", "hrv": "Maroko", "ita": "Marocco", "jpn": "モロッコ", "nld": "Marokko", "por": "Marrocos", "rus": "Марокко", "spa": "Marruecos", "svk": "Maroko", "fin": "Marokko", "zho": "摩洛哥", "isr": "מרוקו"}}, "MZ": {"currency": "MZN", "callingCode": "258", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAeNQTFRFnjsAH4gAAJwAAJoA/gEAwCYAN3kAAJsA/wAA4RIAYGAABpcA9AYAjUUAFY4A/QEAuSoAMH0A2xYAV2UABJkA8QgAhEoAEZAA/AIAsS8AKoEA1RkAT2sAA5oCAJsB7AoAqGItmcmMmNabmtaa/wEA/yAA/wYA/wYI6UBDf1hbV1daWlda+AAA8AgA/3kA/QAA6QAA6gAAywAAQgAAAQAAAAAA4wAAZQAAaBkA7a4A2UcAigAAugAAcgAACwAA/wUA+iUAZwkARRsAtU8Cdl4Ab0YDtTAB+jIA/zAA/w0A+QAAoAAAHwAA/wMA9DoAe0UAs5MpiXM8UU5Dk4dl6MFZ/LMB/18A/woAygAANgAA9iMAfE4nSExRcXFzmZugoZaH8UYV/wIA8QAAjAIDYkhArqyaubmt0s2xmYpztxQUnwAAHgAAdgYC5pAW8LEc7Wkp9aAW8bAPsxkC7wAA/AAA+B4A/4UA/zIA/xsA/30A/D8A/xIA/xQA/wsA/xkA50BDe1hcU1dbVldb/3It/dyM++ma/eia/yEA/4wA/8oC/8sB/8oA/z4A/6kA/8wA/2EA/70A/xwA/8gA/8sA/zgA/6QA/wkA/1oA/7oA/xgA/34A/8cA/58A/00A/7IA/80A////7pRuwgAAAAFiS0dEoF7TvqAAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAE8SURBVDjLY2BgZGImBjCwsLKxE6WQg4OTi5sIpUCFHDy8fIStBynk4BcQJKgSrJCDQ0hYhJ0ohRyiYuJMRCnkkJCUYiJKIQeHtIysHFEKOeQVFJWUcQGYQhVVNQ4OdQ1NLW0cAKpQR1dPVd/AkMPI2MQUO4AoNDO3sLSyttHh4DC0tcOt0N7B0cnZxdXN3cOTg8PL2wenQl8//4DAoOCQ0LBwkPERkbitjoqOiY2LT0jkIKAwKTklNS09IxPE9srKxu0Zo5zcvPyCwqJiAp7hKCktK+eoqKzSJxg81TVAorZOvb6hsQkHQI5Cz+aW1jZcAElhe0dnVzdOAFeY2NPb191NWGFt/wS86mAKJ5ZNmtxNWKHKlKn4jYMqnDZ9BkF1IIUzZ82eTFBdN4NK+Zw+ItR1M8ydN58IZd3dAGl4IBF3SitoAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjExKzAyOjAwHClrIQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToxMSswMjowMG10050AAAAASUVORK5CYII=", "name": {"common": "Mozambique", "deu": "Mosambik", "fra": "Mozambique", "hrv": "Mozambik", "ita": "Mozambico", "jpn": "モザンビーク", "nld": "Mozambique", "por": "Moçambique", "rus": "Мозамбик", "spa": "Mozambique", "svk": "Mozambik", "fin": "Mosambik", "zho": "莫桑比克", "isr": "מוזמביק"}}, "MM": {"currency": "MMK", "callingCode": "95", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAXFQTFRF/ssA/ssB/soB/ssC/skA/tUz/swE/sYA/+eO/csA/csB//bU/8sA/8wC/8oA/9Mr///98MkD8coH78UA9+J2////QrQwQrQvRbUyRbUzRrU0PrIq1O3MMLI0MbI0LrExIawkIqwlI60nI6wmJa0oG6ofRrpK/P78NbIzNrI0MrEvRrlEidKIgs+AfM16f859gc9/dst0r+Ct/f79NLIzNbI0j9WPIqshTrxN0+7T+v36+/37OLM3LK8rMbEwpd2lNrM1JawkcMlv7vju/v/+NbEzObM4I6ohW8FaMbQzNLU2JbEofs9+//z8QKkzQakzQqo2N6QpxOXB/P37//393jE53jM63S0030RK+/Tz96mw7SY57Sk87Bgs9HJ///7+/vX28nmD6Ros6Sk56Sw86Bst97a7+s/T7UVU6Bgr6is86ig56ik66SM07DxL/vT19qGp6iM16SAy6iw96io76R4v8GFt8XJ96BUn6is76ik5/9r9PgAAAAFiS0dEFeXY+aMAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGWSURBVDjLzYzpNwJRGMavZAtZ30SkFKO9NFTaFCpLthKlDVFmsrQhf70ZJt1GndMHH/w+3HPf5/mdB6F/RU9Pl6JA0JXWK+zrE/Z2IfYPDA4O9P/Oh3iIRMLhYaFIxM/RCI9R8dj4+Jh4lJ+jCR6TU9MA01OT/BxJOGYk0lmWOdk8wLxs7uuQMjEHWuCQLyqUSyqVenmFACBWltUq1ZJSsShv9Gj1G41WpzcYTSazZQ2sVlizmE0mo0Gv02o4AZEN1jeYqVaIjfWfuimSNrsDNp2ElVWshHMTHHYb2U50uT1efM/rcbvaiuQW6fNvww4r7cC238cEmLiLoQkEQ429UDCgwTu0h7N/cAgEhMPMc3iw31KhI4zjk9MziEQBohE4Oz05xjt0jhG7uIxDGBIJ5olfXsTwDl1hJFNpyGSvb26usxlIp5J4h24xcnf3kC88UNRDIQ/3dzm8Q3ST4uPT80upXKHpSrn08vz0WMRKXKzWXt/e6Tr7rdPvb6+1ageR/qBrFLdSpGrM2UlkRtt+24id+XvxE+JR8JVjkHRbAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjQ5KzAyOjAwiORAHAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo0OSswMjowMPm5+KAAAAAASUVORK5CYII=", "name": {"common": "Myanmar", "deu": "Myanmar", "fra": "Birmanie", "hrv": "Mijanmar", "ita": "Birmania", "jpn": "ミャンマー", "nld": "Myanmar", "por": "Myanmar", "rus": "Мьянма", "spa": "Myanmar", "svk": "Mjanmarsko", "fin": "Myanmar", "zho": "缅甸", "isr": "מיאנמר (בורמה)"}}, "NA": {"currency": "NAD", "callingCode": "264", "flag": "data:image/png;base64,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", "name": {"common": "Namibia", "deu": "Namibia", "fra": "Namibie", "hrv": "Namibija", "ita": "Namibia", "jpn": "ナミビア", "nld": "Namibië", "por": "Namíbia", "rus": "Намибия", "spa": "Namibia", "svk": "Namíbia", "fin": "Namibia", "zho": "纳米比亚", "isr": "נמיביה"}}, "NR": {"currency": "AUD", "callingCode": "674", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAUdQTFRFACFwAyNvABl2SUtU/7gJ/7gK/7cJ/7oK/rUI/7cISUtTSktUS0xVPUBMZmRkZGJjTk9WTE1VAhh1AAhrR2GkHzeKWWWlV2SkJz6PSmKlAARoBCNvASBtEzB3GDV6KD2AzNHgwMfZxczcz9TiKkGCKEOEACBtACBwgZG4tL7V5urx////7O70xMvefY62ABtsASFwASJwByd0LEeJ3ODr/Pz95OjwRl6YGDZ9AB5uQ1uWpLDM3+Pt/f7+6Ovym6jHRl6XACBvAiNxABRoITmA4ebv/f3+/v7+8vT4RVuVABBlAB9vCSl1gJC4YHWm09jm9fb56+70VWugeImzHTqAAB1tASJxBSd0M0GEmKbGdYaxlaLDdYiyY3CjAANdBSVzABxtLEiJDCRyLkSGU2adABFlL0qKACJxAR5uBiVzCSZzACJwASBvvmnAZAAAAAFiS0dEKyS55AgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADYSURBVDjLY2AYBUMdMBIJGJiIBAzMRAIGFhhgZWXBB2AK2dg5ONg58Snk4uJi5uLm4eHl4+cVEOQGcrADiGeEhEVExcQlJKWEcHsG6HNpGVk5eQVFJWUVVTk1aVzBAwbqGppa2to6unr6BngD3dDI2ETb1FTbzNzCEK86SytrG21bW207ewdHvCY6Obu4aru5m3p4ejnhNdHbx9fP3zQgMCg4JDQMn0rHcK+IyKjomNi4eAJJyCAhMSk5JTXNwICAQob0hIzMLAP9bMLJMixMPSc7jLA6KgIA2H8hEx4mkdgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzIrMDI6MDCAJh3/AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjMyKzAyOjAw8XulQwAAAABJRU5ErkJggg==", "name": {"common": "Nauru", "deu": "Nauru", "fra": "Nauru", "hrv": "Nauru", "ita": "Nauru", "jpn": "ナウル", "nld": "Nauru", "por": "Nauru", "rus": "Науру", "spa": "Nauru", "svk": "Nauru", "fin": "Nauru", "zho": "瑙鲁", "isr": "נאורו"}}, "NP": {"currency": "NPR", "callingCode": "977", "flag": "data:image/png;base64,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", "name": {"common": "Nepal", "deu": "Népal", "fra": "Népal", "hrv": "Nepal", "ita": "Nepal", "jpn": "ネパール", "nld": "Nepal", "por": "Nepal", "rus": "Непал", "spa": "Nepal", "svk": "Nepál", "fin": "Nepal", "zho": "尼泊尔", "isr": "נפאל"}}, "NL": {"currency": "EUR", "callingCode": "31", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACdQTFRFrhworh0prBcjtjI9/PX2//////7+/v7/9ff6NliWHEKIIkeMIUaL9LL7PgAAAAFiS0dEBfhv6ccAAAAJcEhZcwAAAEgAAABIAEbJaz4AAABASURBVCjPY2AYfEAQC2BQwgIYjLEABhcsgCEUC2BIwwKwq6RQsBwLwK6yAwtgmIkFMKzCAhh2YwEMZ7CAgRUEACAv6G1C11PvAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjIxKzAyOjAwkqZswgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToyMSswMjowMOP71H4AAAAASUVORK5CYII=", "name": {"common": "Netherlands", "deu": "Niederlande", "fra": "Pays-Bas", "hrv": "Nizozemska", "ita": "Paesi Bassi", "jpn": "オランダ", "nld": "Nederland", "por": "Holanda", "rus": "Нидерланды", "spa": "Países Bajos", "svk": "Holansko", "fin": "Alankomaat", "zho": "荷兰", "isr": "הולנד"}}, "NC": {"currency": "XPF", "callingCode": "687", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFACZ/ASeApbLS////+6228xkx8xgw8anT+QAAAAFiS0dEAxEMTPIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAaSURBVCjPY2AAAyFjMHBNAwOGUcFRQVoKAgDqd3XHPs6NlAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDoyNyswMjowMB60MsYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MjcrMDI6MDBv6Yp6AAAAAElFTkSuQmCC", "name": {"common": "New Caledonia", "deu": "Neukaledonien", "fra": "Nouvelle-Calédonie", "hrv": "Nova Kaledonija", "ita": "Nuova Caledonia", "jpn": "ニューカレドニア", "nld": "Nieuw-Caledonië", "por": "Nova Caledónia", "rus": "Новая Каледония", "spa": "Nueva Caledonia", "svk": "Nová Kaledónia", "fin": "Uusi-Kaledonia", "zho": "新喀里多尼亚", "isr": "קלדוניה החדשה"}}, "NZ": {"currency": "NZD", "callingCode": "64", "flag": "data:image/png;base64,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", "name": {"common": "New Zealand", "deu": "Neuseeland", "fra": "Nouvelle-Zélande", "hrv": "Novi Zeland", "ita": "Nuova Zelanda", "jpn": "ニュージーランド", "nld": "Nieuw-Zeeland", "por": "Nova Zelândia", "rus": "Новая Зеландия", "spa": "Nueva Zelanda", "svk": "Nový Zéland", "fin": "Uusi-Seelanti", "zho": "新西兰", "isr": "ניו זילנד"}}, "NI": {"currency": "NIO", "callingCode": "505", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAqBQTFRFYpH1YZH1dZTGZJHuYZH2YpD1YpD2ZpLrbJHVcpHAZpHn5Oz95Oz+3+by1NCs39zC5Ov65Oz/3ubZ4Oji5Oz7393J1dK04ej03+bx1NGz5OHI6O765u7/5u7+6O773+DV1dO23+XuZ4/cgpemeJW8ZpHpYpH2YZH3Z5LmfJa4f5WpZI3eU4f0Uof2U4f1VofsYYrUYYnSVYfuVIfyV4fnXIjaZovIW4njc46vdY2lVobpY4nMZYnGWIfoUob0Uob1WojhcY6yXYbTXYfWVIbvXYnhXIniUobxW4fccY2ub422V4blaJDac5PCgrCofqyzW+q5Zum5///////+9fDZ8erM/Pv0///57s13rIZNr4lM8tSG///9+/nx8enH+vbo/Pr07eO09/Ph8ujHjo54cneElJR79u7T8+7X8OjB/v78+PXo6uK5+ffs//3upd2rKcTnLMfiLMTiK8TlseGv//70+ffr7ua/+fft+fbn7uW6+/rz3vW7MvfRCP71GPHkGuziA//6OPjQ5vfG/Pv17ubB9/Tl9/Ti7uW3/Pv3//jih82yQMqQZ8thVM99Ws5yXM1vO8+6lc+2//vq+/r17ObK+ffv//7+/Prv///81dCvWHupXY9TdJU7c5Q9c5Q6dpY3bI1eZICl3di48+zP/fz2+vTa/v35+/PQipixSG/nSnPlSnDlSnLjS3DjSnPiS3LgSm/jlp6u/ffb/fvw+vXc+vjw8+3T+fHL3NvE19jN2NfM19jM19fM19bN39vD+vTS+fTf+vfrXIjbbIq0do6kZYrJV4flU4fxVYbsWofdY4nKaIm7aYvBXYjbUof0WIjnaIvDZYjBaYzEZ4vHbYu0Z4i6YYnTVIfwVYftWIfiXIneXIjfWIflVYfsUof1JwACHgAAACx0Uk5T/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v4R0Yc9AAAAAWJLR0RSDWAtkAAAAAlwSFlzAAAASAAAAEgARslrPgAAAXhJREFUOMtj0CESMAxRhbq6RCjU09HRNzA0MgYx8Co0MTUzt7C0sraxRVdohwzsHRydnF1c3dw9PL28UWTsGJABo48vEzMLq58/Gws7BycXI4okNwLw8PLxC/AICgUECgvyiIiKifMgSTIEIUBwSGhYUFB4RGRUdExQUGxcPJJcELLChMQkIJmckpqalg5kZGRm4VCYnZMbFJSXX1BYVFxSGhRUVl6BQ2FlVXVQUE1tXX1DY1NzUFBLaxsOhe0dnUFd3T29ff0TJk6aHDRl6jQcCqfPyJo5a/acufPmL1i4aHFQ9ZKlOBQGLVu+YuWq1WvWrlu/YeOmzVu24vJ10LbtO3bu2r1n997du/ftP3DwEIpC5ACXkJSSlpEFAxk5eQVFlABHBixKyiqqLCwMaizqGppa2iwokqiJ4vCRo8eOnzh56vSZs+fOoyYKtOR44aLjpctXrl67fkMPf3rU07l56/adu/fuE5EV9O7r6enpEKGQBrmQTgoB+l4eQVzTJI0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MjgrMDI6MDDo/EIvAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjI4KzAyOjAwmaH6kwAAAABJRU5ErkJggg==", "name": {"common": "Nicaragua", "deu": "Nicaragua", "fra": "Nicaragua", "hrv": "Nikaragva", "ita": "Nicaragua", "jpn": "ニカラグア", "nld": "Nicaragua", "por": "Nicarágua", "rus": "Никарагуа", "spa": "Nicaragua", "svk": "Nikaragua", "fin": "Nicaragua", "zho": "尼加拉瓜", "isr": "ניקרגואה"}}, "NE": {"currency": "XOF", "callingCode": "227", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAQJQTFRF/3AA/3EB/20A/3oR/3oS/3kQ/3cN/3gO/3gP//Xu//bu//Tr//fx//////79//r3///+//bv/76L/6FX/69x/+jV//7+//78//Hm/4Qm/2IA/2cC/2UA/24D/9Sx//37//38/6dn/2MA/3UH/3IE/3QG/2oA/38k//v3/4Uq/2wA/3EC/+LN/4ky/2sA/3ID/24C/+bU/7mE/2EA/3MF/3QF/2QA/489/v/+/v79/v///vz6/qJY/mgA/mYD/mYA/oUm/urZ/v78/+DJ/8OW/9Oz//r28fnu8fjt8fjs8f768f//8f/98frv8ffrRKcRRKYPRKUNRKUOMp8AN6EBNqEAvQnPDgAAAAFiS0dEDfa0YfUAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADISURBVDjLY2AYBfgAI5GAgYlIwMCMACysbOxsHCzMWAEDJxxwcfPw8vHyc3NiBQy8cCDAKygkLCLKK8aLDTCIwYA4r4SklLSMrByvvBgWgDBRgVdRSVlFVU1dA2g2FhMRFvNqamkzMGgz6QBdikchEOjq6QMVGhgSUCjPa2RsomJqZo7VL7wMFjBgaWVtY2tn7+Bo5WSBBTCgaHN2cXXD6hWgie4I4OHp5e3j6+eOFTD4I4OAwCB/HIAhmEjAEEIkYAglEoxMhQD8ha3yJTdg0gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDoyOSswMjowME6LSZsAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MjkrMDI6MDA/1vEnAAAAAElFTkSuQmCC", "name": {"common": "Niger", "deu": "Niger", "fra": "Niger", "hrv": "Niger", "ita": "Niger", "jpn": "ニジェール", "nld": "Niger", "por": "Níger", "rus": "Нигер", "spa": "Níger", "svk": "Niger", "fin": "Niger", "zho": "尼日尔", "isr": "ניז׳ר"}}, "NG": {"currency": "NGN", "callingCode": "234", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFNqEAOaIDKpsAvN+p/////P77u9+patqPNQAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAdSURBVCjPY2AAAUZlVxcgCEkSYECAUcFRQRoJAgDKtUNjjeTzkwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNTowMCswMjowMHb0YAsAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MDArMDI6MDAHqdi3AAAAAElFTkSuQmCC", "name": {"common": "Nigeria", "deu": "Nigeria", "fra": "Nigéria", "hrv": "Nigerija", "ita": "Nigeria", "jpn": "ナイジェリア", "nld": "Nigeria", "por": "Nigéria", "rus": "Нигерия", "spa": "Nigeria", "svk": "Nigéria", "fin": "Nigeria", "zho": "尼日利亚", "isr": "ניגריה"}}, "NU": {"currency": "NZD", "callingCode": "683", "flag": "data:image/png;base64,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", "name": {"common": "Niue", "deu": "Niue", "fra": "Niue", "hrv": "Niue", "ita": "Niue", "jpn": "ニウエ", "nld": "Niue", "por": "Niue", "rus": "Ниуэ", "spa": "Niue", "svk": "Niue", "fin": "Niue", "zho": "纽埃", "isr": "ניווה"}}, "NF": {"currency": "AUD", "callingCode": "672", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAf5QTFRFI4cLyuLEy+PFzOPGzOTGGYIAGIIA/////f39+vr66/Dq2uPYwta9mbyR5+zmhq59dqZr4Ofe/P37ucq1V41KQoQzkK+J/Pz72+bYaZldRIM1QoUyaJ5b6vHp/Pz8kbmHRIczRoc2PYYsWZBM3ObZ7vLtb6RjU5RET45ATI88UI1CjbaD8PTvzNzIRI4zWpNMO4koTJI7QY8ulb2L+/z7n7+XSI04Y5ZXXpRRK4MXJ4ERJIIOfKxx6fDn5+/lWptLHoAHM4cfQIsvMYMdK4AXHIEEQY8vvNO2bqJhOYomU5ZDP4stLYIZJ4IRJoUPVJdEo8Kc9/n32OPVeaduYZtTQo8vMocePossVJRFNIggW5dMnL6U+vv6vNG3d6VsTo8+N4kkLogZMoQfT41AM4gfQIoucqNm2+XZp8Kha51fP4otKIUSNogjJ4MSOIolLIMYXZZPbp5jwdS9ydrFbaNgSZE4I4MMJ4QRMogeKIMTJIMOIoEMQ44xYpxUk7mK9/n25e3iZaRWPY4qLYgXL4UaIoMLQI4uLIUWJIMQJYMRRoo2Z5pbc6Fo4urgtM+tXptQUJVASpI5Oo0mJ4cRKokUT5o9da5oosKbl7uPnr+W6vHowtm9w9u+3OnZ4uzghLZ4XqJO8vXx/v7+lsGLWqBJmMKOS5c48/by3+vc+fv5XViUGwAAAAV0Uk5T/v7+/v48tsTrAAAAAWJLR0QHFmGI6wAAAAlwSFlzAAAASAAAAEgARslrPgAAAQRJREFUOMtjYIUDNgZGdhTAyMCGkGUYXAo5OIlUyMVNpEIeXuIU8vELCBKjUEhYRFRMnAiFEpJS0jKyhBXKySsoKimrEFaoqqauoamlrUNQoa6evqaBoZExBwGFJqZm5haWVtY2tgQU2tk7ODo5u7i6uRNQqOPh6eXt4+vnHxCIX2FQcEhoWHhEZFR0DH6FsXHxCYlJySmpael4FQZmZGZl5+Tm5RcUFuFVWFxSWlZeUVlVXVNbV8+EUyFzQ2NTc0trW3tHZ1d3Ty8LHhP7+idMnDR5ytRp02fM1GHCo3DW7Dlz581fsDCGfdEiwgl38ZIY4lL40mXLiVO4YvbKQV8AYFUIAO64TwOh9tA5AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjU0KzAyOjAwJZkhQgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo1NCswMjowMFTEmf4AAAAASUVORK5CYII=", "name": {"common": "Norfolk Island", "deu": "Norfolkinsel", "fra": "Île Norfolk", "hrv": "Otok Norfolk", "ita": "Isola Norfolk", "jpn": "ノーフォーク島", "nld": "Norfolkeiland", "por": "Ilha Norfolk", "rus": "Норфолк", "spa": "Isla de Norfolk", "svk": "Norfolk", "fin": "Norfolkinsaari", "zho": "诺福克岛", "isr": "איי נורפוק"}}, "KP": {"currency": "KPW", "callingCode": "850", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAASBQTFRFPVWVPlaYPFSXqbfV766txwMDxgICxgAAxgEBxwYGxQAAyRAQ2E9P5o6O7a2t7a6u0zs78Lu7/fj4/////ff3/PHx/vj48Ly80zw81UND+N/f8cLC55iY+ODg1UREzB0d9MzM4Hp600RE/fb29M3NzB4e33Jy++/v0Tc3yBIS9NDQ//7+4HR0yAgI77i4/fX155mZ33R04Hd322JixgoKxAEB1k1N3nJy3Wxs44SE+ebm77q6yxUV9dTU88rK1EdHwwICwwAAxAAA0DY29tXV++7u4Xx8xQYG22Rk77e36JmZxAIC11NT2mBg11RU/vr6+ujo1EZG5IqK/PLy6J2d0z8/9tnZ+eTk8snJ//399dLS997e+OHh8K+u8K+tqLfVj+46eQAAAAFiS0dEEwy7XJYAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEESURBVDjLY2AgFjASCWigkIlIwMBMJGBgIRIwsEIBGzsHJycHOxsrDsDADgFc3Dy8fPy8PNxc7NgBTKGAoJCwiKiwmLgEXoVcklLCwsLSMkBCVo4Lj0J5BaASYUUlZSCpoopbIZcaSJ26hqYWkNLW4cKpUFdP38DQyNjE1MzcwtLKWhenQhtbYTt7B0cnJ0dHZ2ttFxt8CoVd3dydnEw9rISF8SjU9QS6zdrE0dHUC8jQw2012DPm3j6+7n7+wsJ4PAMKnoDAoGChkNAwvMEDDHDZ8IhIoLFR0TF4AxwUhf6gsPQnEIXQRMGHL1EQncxiESAuLhY3YIgnEhCfuQawACAWAACoS0iV+rowZQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDoyNSswMjowMIkrI+8AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MjUrMDI6MDD4dptTAAAAAElFTkSuQmCC", "name": {"common": "North Korea", "deu": "Nordkorea", "fra": "Corée du Nord", "hrv": "Sjeverna Koreja", "ita": "Corea del Nord", "jpn": "朝鮮民主主義人民共和国", "nld": "Noord-Korea", "por": "Coreia do Norte", "rus": "Северная Корея", "spa": "Corea del Norte", "svk": "Kórejská ľudovodemokratická republika (KĽR, Severná Kó)", "fin": "Pohjois-Korea", "zho": "朝鲜", "isr": "קוריאה הצפונית"}}, "MP": {"currency": "USD", "callingCode": "1670", "flag": "data:image/png;base64,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", "name": {"common": "Northern Mariana Islands", "deu": "Nördliche Marianen", "fra": "Îles Mariannes du Nord", "hrv": "Sjevernomarijanski otoci", "ita": "Isole Marianne Settentrionali", "jpn": "北マリアナ諸島", "nld": "Noordelijke Marianeneilanden", "por": "Marianas Setentrionais", "rus": "Северные Марианские острова", "spa": "Islas Marianas del Norte", "svk": "Severné Mariány", "fin": "Pohjois-Mariaanit", "zho": "北马里亚纳群岛", "isr": "איי מריאנה הצפוניים"}}, "NO": {"currency": "NOK", "callingCode": "47", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAARdQTFRF1ygo1ysr1Rwc3mBh////qbraACyRBz2aT3K05omK1BgY2Cws1yoq2C0t1R8f32JjT3O054uM1Rsb2C4u1R0d1iAg0xER3Vha5YSF0g0N1iEh3FdZ3Vlb205Q4YKEprjZTnK06KGk2ktN3Vpc+/z+qbzcUHa4/P3+bYzCa4vCdJPGSHC0ADOUAzqYIlKlc5LGbIvCACqPACqQACmPAC+SADmXADiXADSVCD6aCD+aBTyZAjqYACuQmq3Tmq3SmK3TpbnaZ4jBADGTBDuZMF6ro7jama7U5oyN546P7Ly+TnS28trd6qOk7Kqr66iq0QgI5H1/qLjY7Kao0QoK1Boa2C8v5oiJqLnZ7a2u2Csr1BkZ5YaH7aytF4hs4AAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADjSURBVDjL7dLZcgFREIDhzuggBhFGbCGWxBISDCbEFmIn9sSW93+OqHNmXHUpVW591391VZ/TANyVoMNrvUFvxBuTCEdcQjo0cxbrLQtteGd3mAkgcc57lzrR7fFKBPBxD/4APu7DIIbCER8BUPOEz/swijEkQVyTeEmmXlNv6UycBFmNnMsXlMJ7Ts6SoHhQ+lAUpVwq0qByILOJcoUGVVWt/tlofjVb7U6VdPrWXa7n7rN3HOBw9D0mgIkTJlOcsS+cL6QlAUTuR/jFFQvXkmND0G5D3O7UiX/LzbEzu4Tnhf+7jWdEJBC/LQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNToyMyswMjowMAU5fesAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MjMrMDI6MDB0ZMVXAAAAAElFTkSuQmCC", "name": {"common": "Norway", "deu": "Norwegen", "fra": "Norvège", "hrv": "Norveška", "ita": "Norvegia", "jpn": "ノルウェー", "nld": "Noorwegen", "por": "Noruega", "rus": "Норвегия", "spa": "Noruega", "svk": "Nórsko", "fin": "Norja", "zho": "挪威", "isr": "נורווגיה"}}, "OM": {"currency": "OMR", "callingCode": "968", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAASNQTFRF7y0p7y4q7ygj7ysn7ykl7ycj8UhE//v7//////7+7yom8lhU7iYh811a9oqH7h4a8lJP8DYy7iUh8UhF7y8r7iIe9HFu9Xl38UNA9Gto8UpG95WT7ykk7iAc95OR95aU+KWj8D057hsW7ygk7iQg9HRx+KOi9Xd0+8rI/N3c95WS95qY95ya8UdE81xZ+sLB95KQ+ba17zAs9Ghl82Zj7yci7ywo7zIu81tY+8fG+bCu9oaD9oaE7RUQ82hl9oyK7h0Y9Xt595iW8DUy/vj4//39//z882Fe7zEt7h0Z82Rh9X177iMf8D47+ba0+rm4+ri37iEc7S4p8Cwp+Skp6S8pwz8owkAo9iop0DooBZAlAJIlAZElAJElBY4lAJAlAZAl6Dk6bwAAAAFiS0dECIbelXoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADQSURBVDjLY2BgZGJgZmFkZWRlY+fg5MAJGBi5uHl4+fgFBIWE8SsUERUTl5CUkmaVIWQiq6ycvIK0opIyAYUqqmrqGppa2jqsuvgVCunpGxgaGhmbmBIw0czcwtLK2sZWlpDVjEJ29lwOjk7OQsIuruxuOAGDkLsNK7OHp5e3D4HgYfAQZWBkMPJhYPb18w/ADRgYzEQYgABIMAfK4gEMSMAIH2AYriAIH0BSFxyCDyCpCw0LxwMQ9kZERkXHxOIEyArj4hPicQLyFMaOKkQDAKnWwb8cLwtuAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjAyKzAyOjAw4WtxIgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowMiswMjowMJA2yZ4AAAAASUVORK5CYII=", "name": {"common": "Oman", "deu": "Oman", "fra": "Oman", "hrv": "Oman", "ita": "oman", "jpn": "オマーン", "nld": "Oman", "por": "Omã", "rus": "Оман", "spa": "Omán", "svk": "Omán", "fin": "Oman", "zho": "阿曼", "isr": "עומאן"}}, "PK": {"currency": "PKR", "callingCode": "92", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAVBQTFRF/////P38x9nGBFQDDloNDFkLDlsNDVoMDVkMAlACDloMCVcIBVMFYJJfK24qB1UGCFYHD1sOClcJC1cKu9G7fKZ8AFAAJmsmAE4ADFkMtMy00uDSEFwPAEUAdp52aZhoNHQzAE8AVotWY5RjClgJNXU1u9C6BFMDwNTAN3Y3AlIBC1gKRoFGr8mv+/37zNzMOHc3BVQEBlUFI2kj8/fzMHEvAEcAPnc+g6qCO3k7Q35CAVEAGGEXElwROXg4/f79faZ8AE0AEVwQClgKC1gLB1YGHmUd6/Lr3OfcEFsQC1kKAFEAs8uym7uaAEwABFIDRYBF+vv6m7ubAlICBVUEAk8BWY5YJmsliK2I+fv52+bbdqJ2Ong5W49by9vKe6V6AlEChKuD/v/+gamApsKl4Org8vby9Pj04ergnr2eLnAtEV0QJmolJGkjBVMEEFsPgPUI7AAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAE1SURBVDjL3ZPZN8NAFMYvmY1WGNoYxFJCSWyNXVGpXWnttdS+7///myR9wTGRR8d9mXPu/M7c+935LoAfFVCpIBwU8L9BEg6krKp8/gJWR6I1aq1L1SEeBNL6hlhc84o3RkVTAIi05hbhZvXWtnh7h0JlIE90QpfQMTW6Y9CjIemLKNkLfSbCzOofGBwaTiEJiOyRURhLutdkfGJSVyNMAnJzCmA67cudmU3ZVFaamHMZmHeyXn5hkVKparq0vAKra4b/NwyjdakYktsA2MxbWYKQTgtcCvLcFmzDDnN29xRn/6D4ucuvA+eJQziC45NSUT09O0/LxuN54eKynLiCayFV7UmgonRze3f/8PiURzgAdKsXxPPLK3HeAt3jG/fdsG1L/+73n1aBUcowDgGGWoW/CH4A9ssuKRJ51VEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDErMDI6MDDQg2u/AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjAxKzAyOjAwod7TAwAAAABJRU5ErkJggg==", "name": {"common": "Pakistan", "deu": "Pakistan", "fra": "Pakistan", "hrv": "Pakistan", "ita": "Pakistan", "jpn": "パキスタン", "nld": "Pakistan", "por": "Paquistão", "rus": "Пакистан", "spa": "Pakistán", "svk": "Pakistan", "fin": "Pakistan", "zho": "巴基斯坦", "isr": "פקיסטן"}}, "PW": {"currency": "USD", "callingCode": "680", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAATVQTFRFSq3WS63VTK7TTa7STa7TTK7USKzYP6rjPanmPqrkPqrlQavhS63UParlUa/NgbyUqMZnuctSt8tVoMRwdrmjSK3YSq3VRqzancN079oT/+EA/uIB/+IB/+AA4dYkgryUP6riRqzbUa/O0tI1/+IA/t8B/d0C/N0E/N0D/t4C/uABsclcQ6veSa3X09M0+90F/t4B/94A+90E/+MArMhipMZserqdQKrhR6zaWLHG8NsR/98A/d0D0NI4RKvdP6rkib6M+t4FY7S5PanlgLyWi7+JgbyVib+L+94FY7S4WLHF8dwQ0NM3pcZq/94Be7qcQKri1NMzrshgTK3UUq/N1NIz/t0Cs8laR6zZn8Ry8NoS4tYihL2RU6/MhL2SqsdkvMxPustSo8Vtd7mgSKzZQarh////G2crvwAAAAFiS0dEZizU2SUAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEZSURBVDjLY2AYaoCRiZmFhZmVoDoWNnYOTi4ObgYmvMp4GHj5+AUEhYRFRNmZxHCrYxUXl5CUkpaSkpGVk1dgYMRpnqKSsoqqmrqGhrqmlpS2Di51Yrq6eir6BoZgoGZkbMKOw50snKYq+oYwYKAhZWaOVSWjhaWVtQFcoaGNlK0dVley2DtI2RgiA0cnOx5sCp1NpDSQ1alJuWB1JQunEKpCGylXThbiTHTDYaI7mhs9PLG6kVHcyxvF17I+2H0NNNIXKRz9NKT8A7CHOKMuQyByzAThihmGYMWQUGhch2lJhesw4Ew/rIoRkVGg1CMtGx0TgDv1gNNjbFx8QmJSsi47Kx51IB+lAFM4J0cqAwsDAUBsnhnqAACZWDZBbLPFFQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDozOCswMjowMCRWQrEAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MzgrMDI6MDBVC/oNAAAAAElFTkSuQmCC", "name": {"common": "Palau", "deu": "Palau", "fra": "Palaos (Palau)", "hrv": "Palau", "ita": "Palau", "jpn": "パラオ", "nld": "Palau", "por": "Palau", "rus": "Палау", "spa": "Palau", "svk": "Palau", "fin": "Palau", "zho": "帕劳", "isr": "פלאו"}}, "PS": {"currency": "ILS", "callingCode": "970", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAMBQTFRFAAAAHQAAAgAA1wAAgQAAIwAAAQAA/wAA/gAA3AAAgAAAfwAALAgIHiAgHyEhHx8f/QAA6Q0N4GBg4L294OLi4OPj4ODg/wIC/yUl/4GB/97e//7+/////wEB/yMj/39//9zc/yEh/+rq/4CA/wEC/yQl/4CC/93e//7/6RoN4HNg4NC94PTi4PXj4PPg/gEA3BUAf0wALIcIHqcgH6ghH6Uff00AI4QAAZoAAJoAAJgAAJkA1xgAgEwAHYgAApoAZyEukgAAAAFiS0dEHJwEQQcAAAAJcEhZcwAAAEgAAABIAEbJaz4AAACnSURBVDjLxdDHDoJAAIThtY5d7AUBe++9+/5v5XoBVFbnYOJ//pJJRoj/5fOTMBAMhSmISDRGUYCkAEkBkgI2jSeSqbQqBwJaJpvLF4reuSFQKleqes2zZwgYplXXGaik71DShtX8JTTMFjOtYK+w3en2+t/v0QbD0Xgy9c6Bs/liuVpvVAmbbXf7w1GdcLHTpwTHHpBiEp4vDJPwemOYhByTkHQ8vAPna3mr2AmzewAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDozNiswMjowMHRpOewAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MzYrMDI6MDAFNIFQAAAAAElFTkSuQmCC", "name": {"common": "Palestine", "deu": "Palästina", "fra": "Palestine", "hrv": "Palestina", "ita": "Palestina", "jpn": "パレスチナ", "nld": "Palestijnse gebieden", "por": "Palestina", "rus": "Палестина", "spa": "Palestina", "svk": "Palestína", "fin": "Palestiina", "zho": "巴勒斯坦", "isr": "השטחים הפלסטיניים"}}, "PA": {"currency": "PAB", "callingCode": "507", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAY9QTFRF//////7+6Fpa4CUl4jEx4i8v/v7/4jU12AAA2gMD2gAA4zY23AQE2wIC/f3+0tLw7e352QAA2wMD2wAA+vr9V1fGs7Pl+/v+/Pz+8/P7BwesbW3O8fH6ZGTMLy+7NDS7AACpEhKwOTm+LS26paXh6Oj4UFDFAACnBgatAQGqDAytm5vf8PD6GBiyAAClAACibW3Pra3ihYXXRkbCGBiv9/f8h4fYtrbngYHWxcXs+vr+9/f9/v7+/v3+4jU22wEB+fn9+fj8+v//4jg53AgI3AYGQ0PBGRmzISG2Hx+1ICC1ICC2Ghu0zbfX/+jh+N3f+9/fKCi2AACmAQGpysrtKiq5AwOsAgKsAACqysns/Pv8//39//z8KSm4AQGrAACr+t/f/vn5/vz85FNT+dnZ+t3d1gAA75mZ/vv7+uDg5VVV4Tg43zEx1wAA3B0d4Tw84To69Lq6+djY4kFB2QYG3B8f9La2+NDQ1gQE1AAA7YiI6HBw2xYW9bq65E9P3Scn75iY/Ozs8aOj+dvbAgKr9Hz2CgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFPSURBVDjLjZFXU8JAFEa/tawaNSqiYI8VK1bsvfeGgpqAioqxYO+9/XAjo4EsknCedmfOnL1zF4iAIC4+IZElTKD0T0xKTuFY/hVTubR0FlXjkZEJ+lvMMmWzqDkzcnJhMRStVuTlF8BiproiRWFRcYlQWlZeoZz1ihSVVbbqmto6UIOnKeob7I1NwSH1Z0SzvaW1zWFY5NHeAXR2KYdoIg0C3oFunu9Rrr196OdMAyyaf6HQ2aN5kGVoeIQbHWPB+MTklIbpmdm5+YVFFiwtOwUtTtuKsoUIVgWXW4trbR0bYhBJVIFH8G5q8QpqkZBQUUck2NqORRR92NmFZChKEvb8+5BlfZHg4PDoOHByenYOUbdIcHHpvwpc34AYPY3bwN39A2SjGWU8Pj2/vIqGRfje3oGPT2ORECKrvR/xK9rCJYLQGvW/MByP4I5J/AZBtblUjIAx0gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1MSswMjowMHehDuUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTErMDI6MDAG/LZZAAAAAElFTkSuQmCC", "name": {"common": "Panama", "deu": "Panama", "fra": "Panama", "hrv": "Panama", "ita": "Panama", "jpn": "パナマ", "nld": "Panama", "por": "Panamá", "rus": "Панама", "spa": "Panamá", "svk": "Panama", "fin": "Panama", "zho": "巴拿马", "isr": "פנמה"}}, "PG": {"currency": "PGK", "callingCode": "675", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAhNQTFRFWgAAAAAAAAAAAAAAAAAAAAAAAAAA1wAA/wAANAAAvAAA/QAAAAAAFgAAjgAA9AAA/xYA/z0A/wcABgAAXgAA3wAA/woA/4kA/7IA/x8A/zkA/8YA/8oA/00A/1sA/84A/80A/3MA/0kA/1AA/wwA/xoA/xcA/8wA/8UA/8kA/3UA/w8A/gAA/wgA/1MA/6YA/8sAjwAA/wEA/zAA/1kA/5IA/9AA/7EA/xwAAgICAQIC/ygA/7UA/6kA/74A/0cABAQERUVFRkZGBgYG/wMA/3cA/9IA/88A/8IA/7QA/4IA/2wAPj4+2dnZ3NzcQUFBFwAA/wsA/2IA/4YA/2cA/zsA/58A/50A/yYA/4wA/5gA/xIAGBgYtra2ubm5HBwcXwAA/wIA/zIA/6UA/8EA/64A/xQA/2EAAQEBBwcHGRkZFhYWCAgINQAAvQAA/xUA/y8A/2YA/1IA/5MA/3EA/4gA/wkAJSUlSUlJBQUFDAwMISEh/ysA/04A/ywA/30A/wUAIiIis7Oz4ODgWVlZCQkJgYGBx8fHOzs74AAA/yMA/1gA/38A/y0AmJiYyMjINTU1fX19Ly8v/x4A/zEAAwMDGxsbDw8PEhISFRUVkAAA9QAAEBAQYAAAX19fKysrCgoKFxcXY2NjNgAAvgAASEhI7e3t19fXkQAAh4eHd3d3BwAAGAAAYQAAHQAAmAAA////z7dyKwAAAAd0Uk5T7e7v8PHy84GHKKoAAAABYktHRLBDZK7EAAAACXBIWXMAAABIAAAASABGyWs+AAABtklEQVQ4y2NgYOcgCjAwcnJxE6eQh5ePH5uMgKAQmkIeYRFRTHVi4hKS6Ap5eLBYLyUtI4upEIv1cvIKikrKKgiFTGCFaNYLqarJKahryGhqaaMrBFmvA1eoq6evb6Curq6gLoepkIfXEGa9kbGJqbw6CJiZW2AotLSCW29to6CpAFKnYWtnj67QwdHJGWq9i6ubu7wH0GoFTy9bb5hCZqhCH18/fx6eAJD1gUHBXiGhYWby7uERkVHRaApjYuPiQb5PAFmfmMjBkZQskZKaZqKZjqwww5InMys7B6wjNw/q+3yLgsKi4pLSMoTCjPKKSrCiqmoeqPVgkFhTWxda34BQ2NjU3JIBpFvb2jvA1nfCwqmru6cXajULUMa5r38C2MSJ7ZNQrDeaPCWaA0nh1Gkx08EKZsyEuJMnYNZs9PTIwoMAczLhTOG5nbgVZsybj6QL7nssJlZVInHQrEcoXODMgwZQrGdghYounLhoBrpKnsVLdDAULl22vBxDIU/AitnoCrNWrqrCVMizGmY9XOHUqVMzeLABqPVwhbjBGrD1DGwEFfKsXttJnEKg9XncxCnkWbceAHS+f+1YQ5uiAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjAxKzAyOjAw0INrvwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowMSswMjowMKHe0wMAAAAASUVORK5CYII=", "name": {"common": "Papua New Guinea", "deu": "Papua-Neuguinea", "fra": "Papouasie-Nouvelle-Guinée", "hrv": "Papua Nova Gvineja", "ita": "Papua Nuova Guinea", "jpn": "パプアニューギニア", "nld": "Papoea-Nieuw-Guinea", "por": "Papua Nova Guiné", "rus": "Папуа — Новая Гвинея", "spa": "Papúa Nueva Guinea", "svk": "Papua-Nová Guinea", "fin": "Papua-Uusi-Guinea", "zho": "巴布亚新几内亚", "isr": "פפואה גינאה החדשה"}}, "PY": {"currency": "PYG", "callingCode": "595", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAORQTFRF2RUV2hUV2hYW+NbW+dbW9NPT7c3O39z24Nz329n01NTuTTfOTTfPTjjP1gAA/////f7+5+rq2MLB2Kea2KOa4uLi2ZuN32JJ4n9m5n5r4WRK2ZyN4uHi8/X217Oq22Y+joZokJyusrfRq6OF3GZA1rCp6Orr3Y94uHVRV3V2f2eDjHF4cpGSxIRd24d33Ip3w3ddWXp1d2OReWaFYYl9xn5h24V38/b21aqp20ZFnYx3g56ZeZuRlY9v2kdE1aup1o2N3UtL1GRc0mNa3EpK/f796ero2cC/2JmY2JqYPCTKPSXKNU1p8gAAAA50Uk5T/v7+/v7+/v7+/v7+/v7BShreAAAAAWJLR0QPGLoA2QAAAAlwSFlzAAAASAAAAEgARslrPgAAAKJJREFUOMtj4CMSMIwqpI5CTMDIxMSIRZgZHbCwsrGxsmAIM/CjAQFBIWERIUEBdHEMhaJi4hKSUtIyhBTKyskrKCopq6jKElCopq6hqaWto6unRkihvoGhkbGJqRkhheYWllbWNrZ29uYEPePg6OTs4iBK0Neubu4enu5urhgK2dEABwcnFxcnBwe6OAM3BuDh5eXBFGXwIhIweBMJRhXiBQBnBptBvd+qpgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDoyNyswMjowMB60MsYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MjcrMDI6MDBv6Yp6AAAAAElFTkSuQmCC", "name": {"common": "Paraguay", "deu": "Paraguay", "fra": "Paraguay", "hrv": "Paragvaj", "ita": "Paraguay", "jpn": "パラグアイ", "nld": "Paraguay", "por": "Paraguai", "rus": "Парагвай", "spa": "Paraguay", "svk": "Paraguaj", "fin": "Paraguay", "zho": "巴拉圭", "isr": "פרגוואי"}}, "PE": {"currency": "PEN", "callingCode": "51", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAA9QTFRF/6qq/wAA/wIC/////5CQg6lnGgAAAAF0Uk5T/hrjB30AAAABYktHRAMRDEzyAAAACXBIWXMAAABIAAAASABGyWs+AAAAGUlEQVQoz2MQBAMFYzBwhPAYRgVHBWkpCACA3EcjIZL0PQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1NSswMjowMIPuKvYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTUrMDI6MDDys5JKAAAAAElFTkSuQmCC", "name": {"common": "Peru", "deu": "Peru", "fra": "Pérou", "hrv": "Peru", "ita": "Perù", "jpn": "ペルー", "nld": "Peru", "por": "Perú", "rus": "Перу", "spa": "Perú", "svk": "Peru", "fin": "Peru", "zho": "秘鲁", "isr": "פרו"}}, "PH": {"currency": "PHP", "callingCode": "63", "flag": "data:image/png;base64,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", "name": {"common": "Philippines", "deu": "Philippinen", "fra": "Philippines", "hrv": "Filipini", "ita": "Filippine", "jpn": "フィリピン", "nld": "Filipijnen", "por": "Filipinas", "rus": "Филиппины", "spa": "Filipinas", "svk": "Filipíny", "fin": "Filippiinit", "zho": "菲律宾", "isr": "הפיליפינים"}}, "PN": {"currency": "NZD", "callingCode": "64", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAACaxJREFUSMetlnlY1VUax99zfud393u5G3iRRQF9kCS3BHclUNRxy0wxzZYxG2psc0zFJdtMB5tKLTNtNG0hiewpTcVdVFxANEUc1Adkv+DduPtvPfMHaE5jTc/U+/c553POe97v933RpbQ/xa/Oi8gaDgDBALdu++l3Pil11buQXsWoiCRRSikAAABhsOgKzJgzeGf+xIs9M6jHAwDIaOx3/VjOoj2Fn50hZq0oyR2LEUIMg6QAR31ha9+ERbkjn7D43a+u8ZVfMj0yMXLBX3B0YnT9lMersnJ8peUarTLvuYwr++fnLc42mtSiIwCiRAhGCMFvDoQQIRg4XmzzmrvbVuTnnF3Y7/Fv1jdNnE0N+h4HdyYVbDSk9cUfjJmnK9xus2prRj5UOWqa58Q5W5Th7Veyrx18cXlettGoER3+2/j/iYROZKvX3N22cu3MC0sHvHRos3/qLMctb8KhouTiAmPGEPst34p3D+HXF+1MW31+XXaucfdX0TZjXda0ylHT3CXnLBbdmwvGVB94YfmSsbfxMhB8Tz5GCAgGThBbveZutpVrZ1YsGfB88SbXpFnNrd64I7vuO1ZkHJHmcPiW/+NgSvb6t1bvx8poo6fp1htLCtPzKzaOz7Xu+Sou1tw8dvq/Mqe3nzhntere/NuYa4dfWr5svNGoAYefChL8jI0QFSRwBsxJMSvfmVmRN+D5A5vck2fZ23yJx79NPVZkGpHucgVee/9QyrgPVq096PNzqi4GBJELAQEwGAU56uPUqfFznxjydFde++k2d/Ex3cghUQtyTeMzsELh9gRfff+wAHjT0swfUzJltwcAsMnY9+qR3NVHGZ5b3Eeh2bmzafcRMmxw/KrF+mFpAHCj1vHu1lNbvyjjGt1Ir6JKApIMFNCMBUV30sVgHApy7f5wj97dXnxqaFLd1cb1W5EkGSaMjpg+iRAGKOURwp72q2njRacbAIjFlFK2n0YY8P4D9q/3BjnRNv8p/ZAHqEwRRpevt234vKymwW3UK1UqVpJk+bZGEBXDP68QjCEYFoICMeoRIUCp5PODJMmAZElm1UrR4arOmi66PABAzMbkw18Ti0ngBdZowBiDIFJPgGIZYUYEymoUoGRBluE2spNzIWHwPeuTwZhKEgAFQMBgAARweyelktffeRBCjEEHGAOlVJIxpRxBVVZJG4AokRgFLFEqy/S/C5J0+MDPggLIv64chvnpEh43FZFMARFQSqhRJ9bIQrVFyvLQPnoWrrBKBZYAAFEZAChgQACAf7sz3PsGAFJAidREr8dYQiFG7uUhVlFhoei+FnOTL7I5zhhmMKaAeIVOVERQBcEEAAgyGn8x1XdyK8n/kXMqd6QaAQgyRMxqt2f5d+0zPHzcqlGzDomn8Ql9jBGn0tV6dzDhaqNGACShy/0dpyWpawMzrEWtUxt/obg4QQjwGAOV5I5fRITIsix4vKyCFZ3ujuLCwISNbMoB/khD4rpj4aOL98qy5HC5l61bKYPPICsHDR09c9JsIYR4vpZKY14pTjxy7cf8MW9NyZxHchbt+UlODA6HBY+PS4wzvfBYemrPyA5V+ErLWz78VKsk0TMmiGPHYLHTQ2Qka0VsMHzVGDo+sGvlRyc+OnWhZGHscKv5fJvktgeFfZeqLjnog4mn7E6dRWnu6U0tOJm80dtStG8vKfzsTKeBcCL1hZVxprmz0xY8OSgpwQoA/tLy+mVrxNKzMZOzApNz5pf45LN7N+U92KFHlYyP27jCg0V1zVdrHRd3XyicNHhCDaNIYDL4hlsaf1NS+pVUnXd4f0eba4e7VVvxo1rlG1h+2OW6dZSoovQCJ0q+sCXROn92+vNPDDabtADgKy2vX7pGLD0bOzXb/33Bhhblx1vKWk9czckdBYShFChQFjAvhD46sTZOo62vHOy8MmVk5PCLiuriXQd79UyxJAUucrRm6ye9/mlTLszvM9SuUm9+dEJZ9suzXXIiCbd6TbGmvz474oXHh0RadRTAXXKuaUW+dPps7NRs/3dfrm9WfLj6jLOyjjWqwaJFLNOhYAwogISRfuu+eVvePvSarimsGuqqqPpw3iN16WlMr26pbXwsbpaUc25uzndlPtxw4hnbhDz1+YbXPUQCZw1ZsXTcc7MH2aL0v4JEeiXpYgAA8POyTIECBaCAKACWYGC/YXTH00DyX32sKj3ZV+2kJ0/eHwrwUSoV6Pp3M7RsmGPxkR4ZMX77tMgY/eWyvJk7mtPIGy+PBgB/yRn7qvdDx0q7PjTG/13B+mb2bqQkUVGUCYM7lYuAgAwgA6WEwZzb8XR2SYzKecMd/8DWZ256mUxojoyrXVMzqp2zMiczuzB7hWShivQOT7kv5YcqdvrcFyeOIHVHyxwbP23/vljXP9VYuGODU/PB6lJnZf3dyDt2BggEXuLbw25QUKTEWOZFHXf5ufSEI29vG7H7hqGma2+KdOU6+8t93htEbNvP9wcmdlB0o9XrbhWNm5t3zno2fqF1w+VlHyNd15eoKDIGnTLGFuYk37UmpFUwWuXd09ZdXkoVCqLTsGK7DyhFCHECHTb5W09I47web+rffEl6R+ItKzM+djYW9+luWX3xjdq2mFnJ2x6N3/z3r3PHD7hijjy/ef+z15ssDB89TlBrOWCCDi/vD5IIDWUYWaZwz0BIkuRQQOAwyyGWQ1iUNXaPThVd2617bXVlelCT2Sf+5ljbphljLzjbTusUVWfcGZdL5IigM3f6D4+MvnmpOuWTLYmy5CRIljoPVRIAuDMm/rI7I8RioBSAAqVAuNCtGJN9wNSx548d7gqJaHbKN99f9gT4NQUXanP616V2mVOpFAoOdT8nNYffG+htt4AJZFbBgHnU/98hEFCRREe2v/bnsolDHGGesGZLSsS2tluQrChGTLjeHRqVTI5U6BKTdjHmkN3J2pviEMGU/r7uRCkCIrW4IuauGlddZ/hib++siC8PXhMaAkpRawPMnm6IUAl7evLO+hv9/C1R7rq+jIJSGQDgd724IxiGcl4tT0VWZHZXMEnJtf6wUqOQKlpZwkoXq/XXz93fq1dNY333kDuWIhkQwO/vxwAgyQi0fNGh1Ikjq0LOONbd5ckBbobiB7oIM5P9F0uGj0qvmZHW7q3rwagDd3b9AWAAYLDMBVXJ3RxLcs7sKpj4+amogQntBjW3anu2SrRsWfJto9MAnU/tDAQ9VvwhbKAoKbqtrV3n8xuACcamlLfb43ytiUDEvj0ag2FyvT4asSK9PX79cWAA4FkgEjASogzlWWBkRHgKCEIKYGRQCHDX0Pdvk5Hdf8eaPikAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDQrMDI6MDCCu0QYAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjA0KzAyOjAw8+b8pAAAAABJRU5ErkJggg==", "name": {"common": "Pitcairn Islands", "deu": "Pitcairn", "fra": "Îles Pitcairn", "hrv": "Pitcairnovo otočje", "ita": "Isole Pitcairn", "jpn": "ピトケアン", "nld": "Pitcairneilanden", "por": "Ilhas Pitcairn", "rus": "Острова Питкэрн", "spa": "Islas Pitcairn", "svk": "Pitcairnove ostrovy", "fin": "Pitcairn", "zho": "皮特凯恩群岛", "isr": "איי פיטקרן"}}, "PL": {"currency": "PLN", "callingCode": "48", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRF//////7+/e7u4RER3gAA3wEB3wAA/OBH4QAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAlSURBVCjPY2AYwUAQC8CuUgkLYDDGAhhcsACGUCyAIQ0LGCGCAM5zc403UJE7AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjE2KzAyOjAw2Y5VrwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToxNiswMjowMKjT7RMAAAAASUVORK5CYII=", "name": {"common": "Poland", "deu": "Polen", "fra": "Pologne", "hrv": "Poljska", "ita": "Polonia", "jpn": "ポーランド", "nld": "Polen", "por": "Polónia", "rus": "Польша", "spa": "Polonia", "svk": "Poľsko", "fin": "Puola", "zho": "波兰", "isr": "פולין"}}, "PT": {"currency": "EUR", "callingCode": "351", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAYAAABe3VzdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAAA9hJREFUWMPt2ElsVVUYB/Dfve+1pa/vQaG0SGmLtEDQCI4QA6JGY0gc4hAwBhNjInGhLnShC2WhOCW6MTEuXKlRExOjJgTBhUNUVESFqAytDC2UQlsohba0b74uqi5IqJbyNDH8tzc355dz7jn3+04QvB5ExpuAKOD5j3hy4/hfH0/ikbMYIPoDWVLaaMJ/YYzzwPPAcwoMUInJGUZCBpIU4qUDBl7/Z5sxRHOMG1IsmUbhGJUjTM8ya4Dkl1zwLYn+/2AGq7A6xasXcfdsBrEnQ20dlZ2EszjxFLtfpuvK0TPyXOVvF6cKD09nzdwGHUem+rA3sjfLBRmmJmnYzPTP6F3K8J2RnhfbZdYNm7N59HMoKbACdxRYEgsM7prj2kSPpa1Z4XsEKyhrGEWEaRoXFg3tb7S9vlbHfQfE+pm9s8TA5jSrmqlMBIITIyoaD4iHGfn9yFCYSbGf8CjlTcQGyhz5OVKs58DN1P9GWa6EwGW1yHP4CC19eLaobUqDoUdX6o+V6X03Mu+WUGH/Hgsf/EhiJou7Ob6M7L30r6eurUTAyohFCTrzfHGAi0/Ssr/I8maLXnzeL78mtG1hwf30f7VB7v31oopI3+0cizM8RGYRNW3ESgFMFajKUJ1g5aVUd+Me0jG+/45Mjskpdu1koIPFdxG1MLyCfJ6yAsVmigGxCVQVYy5xscihw5xIM68FL5D+mB+3kqwmm2XbNoIuFj+EGqIPyHVRdSvJNPEJljxnBGbjTKmjs4KXDkZSuwNXvRWYEgy6Ze2I4VzCjh0su45jm46LrSsIm0ItW1gwQPc9VKQnftSc8aA+hdZ+ZuRZFEamFbApofbbXXK/vC2bHTE4HMkd22PS1jekNpPuSmhfm/XTGtr7CXdPUDfWDObw9Qkeq+eZeYQn+wwtn61u1a/y0VqDz23QMlwrt2WbeQ/8JlaVcHRGtb6aVvFrmNRDaoI7eEwgbM3zQw8Lqnhrz0Ff37TE4+Xz1bftNfPTz8gQzMeKpOMNlztY7FA5klVWQ/gBdb0TB8bc5ukzPcwEDITcNodZ9UVv5np9k200pX2upMnKZ9QwaY6w9UKFL9qldSq/keR3XPYO5emJA/+2mgmwrJwnmqks58PuwPaRpKZT0zxSG1f72inV+hxfkZNZjs+Z+wqpnonj/hHwz8yNsbqG62dQHqejk5oEhqhrJDpE6n2aNjJp8NzgxgWEMjSFXFJJcogrDrJ0Lw3dVO+iqo+zaGLHzLhq4Rz2Fdl3CgHzt3H1J+cWdHrOvicJif0LHc3/r2k6Dzwt8eBsfufBX9czSn1D8zt5hVO5+XSEIgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDozNSswMjowMEWBI3EAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MzUrMDI6MDA03JvNAAAAAElFTkSuQmCC", "name": {"common": "Portugal", "deu": "Portugal", "fra": "Portugal", "hrv": "Portugal", "ita": "Portogallo", "jpn": "ポルトガル", "nld": "Portugal", "por": "Portugal", "rus": "Португалия", "spa": "Portugal", "svk": "Portugalsko", "fin": "Portugali", "zho": "葡萄牙", "isr": "פורטוגל"}}, "PR": {"currency": "USD", "callingCode": "1787", "flag": "data:image/png;base64,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", "name": {"common": "Puerto Rico", "deu": "Puerto Rico", "fra": "Porto Rico", "hrv": "Portoriko", "ita": "Porto Rico", "jpn": "プエルトリコ", "nld": "Puerto Rico", "por": "Porto Rico", "rus": "Пуэрто-Рико", "spa": "Puerto Rico", "svk": "Portoriko", "fin": "Puerto Rico", "zho": "波多黎各", "isr": "פוארטו ריקו"}}, "QA": {"currency": "QAR", "callingCode": "974", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAQ5QTFRF//////7//v7+/fz93MTYpmyeeBxqXABMWgFJZABVZgBX/fv94s7fq3Kibg1gZQBWZgFY//7+6drnwZi7lEyJawldZgFX/Pr8u4+1eB5rWwBKVgFGXQBNw5y+l1KNchRkYgBT+/j6wJa6cRJjZwFYwpq8l1GMdx1qaANZ/Pr7tIGsaw1dUQBAVAFDXwBP6Nnmk0uIdBdmZwJY+/n7xJ2+mlaQdRhnvI+1eB1rWgBJVgFFXgBO/v3+5tbkvJC2j0WFagdc6drmsHuobw9hZQBV0LDLmleQYABQ0LHMmVaQcxVlXgBP5dTjrHWkbg5gwZe759fkvZG2kEWF5tXkrnmnbw5h3sjbqnGheyFuWgBKSus3HwAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADMSURBVDjLY2BgYGRkYmRmYWVj5+Dkwg0Y4ICbh5ePX4CwQkEhYRFRfjH8ChkZxCUkpaRl+AlbzSQkKyevQIRCkKmKSsqcKgQVigupqqlrEDKRkUFTS1tHV48Yq8X1lQwMjYhzo7EwEW5k0jcxNeMkaDUwHM0tLK2siQoeG1s7ewd+IhQyMDg6ObsQdCOzq5uZOzFRyOzh6eVNjEIQ8PH1cyEimTHp+48mM3xuHLBkxhQQGERcMmMKDgnFF9eMTExMNjbMYeER1pH4CikAXrE6beIEaK0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTMrMDI6MDCLtnoIAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjEzKzAyOjAw+uvCtAAAAABJRU5ErkJggg==", "name": {"common": "Qatar", "deu": "Katar", "fra": "Qatar", "hrv": "Katar", "ita": "Qatar", "jpn": "カタール", "nld": "Qatar", "por": "Catar", "rus": "Катар", "spa": "Catar", "svk": "Katar", "fin": "Qatar", "zho": "卡塔尔", "isr": "קטאר"}}, "CG": {"currency": "XAF", "callingCode": "242", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAGxQTFRFAMoABMsAAMcAJdAA5voA//8A/v8A//0AA8sAAMgAIdAA2/gA/f8A//4AItAA3vgA/P4A//wA/+AA/9oA/yQA/98A/yEA/wAA/94A/wQA/wMA3/kAJNEA2vcA4fkA/9sA/yAA/+UA/yUA////3y6slQAAAAFiS0dEIypibDoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAACFSURBVDjLjdQ5EoJAFADRDzooKOJKi7ve/5BGigZT1R2/uCMyFeVkmqr0aZZz87pZpOXo2pwrV11af90mbXOu+Xe7vXOHHuWOPYN0J6zDOqzDOqzDOqzDOqzDOqzDOqzDOqzDOqwjilo5omzOxhGXq3LEzTmi+vlGuj9yjhhdm54vhozjDTVWMskJsLmPAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjEyKzAyOjAwLcFxvAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToxMiswMjowMFycyQAAAAAASUVORK5CYII=", "name": {"common": "Republic of the Congo", "cym": "Gweriniaeth y Congo", "deu": "Kongo", "fra": "Congo", "hrv": "Kongo", "ita": "Congo", "jpn": "コンゴ共和国", "nld": "Congo", "por": "Congo", "rus": "Республика Конго", "spa": "Congo", "svk": "Kongo", "fin": "Kongo-Brazzaville", "zho": "刚果", "isr": "קונגו - ברזאויל"}}, "RO": {"currency": "RON", "callingCode": "40", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABtQTFRFpaE385sGADGcATKb/+EA/94A3iIQ3iEQ////bEiZPQAAAAJ0Uk5T/v6mqd4QAAAAAWJLR0QIht6VegAAAAlwSFlzAAAASAAAAEgARslrPgAAABxJREFUKM9jUAIDA9dQIAgRKwcDhlHBUUFaCgIAAIWtj9wIsGoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDYrMDI6MDAVJFUxAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjA2KzAyOjAwZHntjQAAAABJRU5ErkJggg==", "name": {"common": "Romania", "deu": "Rumänien", "fra": "Roumanie", "hrv": "Rumunjska", "ita": "Romania", "jpn": "ルーマニア", "nld": "Roemenië", "por": "Roménia", "rus": "Румыния", "spa": "Rumania", "svk": "Rumunsko", "fin": "Romania", "zho": "罗马尼亚", "isr": "רומניה"}}, "RU": {"currency": "RUB", "callingCode": "7", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACpQTFRF/////v7+7u72EhKHAAB8AgJ/AQF+AgF9AAGAEgF17QEK/wEA/QEC/gEBEp+r8wAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAABBSURBVCjPY2AYfEAQC8CuUgkLYDDGAhhcsACGUCyAIQ0LoFSwHAtg6MACGGZiAQyrsACG3VgAwxksgOEuFjCwggAMe/hdiobzpAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1MiswMjowMEZJFHgAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTIrMDI6MDA3FKzEAAAAAElFTkSuQmCC", "name": {"common": "Russia", "deu": "Russland", "fra": "Russie", "hrv": "Rusija", "ita": "Russia", "jpn": "ロシア連邦", "nld": "Rusland", "por": "Rússia", "rus": "Россия", "spa": "Rusia", "svk": "Rusko", "fin": "Venäjä", "zho": "俄罗斯", "isr": "רוסיה"}}, "RW": {"currency": "RWF", "callingCode": "250", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAT5QTFRFAJr0AZr0AZrzApr1A5r0BZryAZr1Dp3qKKLTNaXHM6XJJKHWC5ztAJrzAJr1EZ7kP6q3d7iCo8VYoMRbbrWLNqjADJ3qBpvvN6i/kr9p4NIdushDwso729Ejfrp7LKXKApryAJr2Ep/iXbGb2dAlpMJYzcwwvcg/scZLy80yS62sC53pGKDdeLiB0M0uqcRV/+EA/94AosFc088qXrKaEJ7kE5/iYrKW2NEln8Fc2c8kyMs1q8RRz84uTa2qDJ3oB5ztO6m8msFh3NIhs8ZKuchD39Ifh7xzL6bHA5vyFZ/hRauyhLt1schLq8dQPKm7Dp3nEp7mLaTOO6fBOqfCKaPRDZzqAprzApr0CJvvBZrzAJn4AJj4EKDjEaDj3+kR8/AA7u8B7+8A7O0B/vYAhr8ZDYg1H5EwHI8x////6TFM6gAAAAFiS0dEabxrxLQAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADVSURBVDjLY2CgDmBkZGQiRh0zCysrCxsjYXXsHJxc3DxsvATU8fELCAoJi4iK8eFXyScuISklLSMrJ6+AVx2TopKyiqqauoamljYffoU6unr6BoZGxiamivi8zsRnZm5haWVtY2tnz4fX43wOjk7OLq5u7h6e+D3Ny+fl7ePrp+sfwEcofJgDg4JDQsPYIE7BA8IjWCMjoyLAbIZo/CAmBspgiMUP4uKgDIZ4IgFDApGAIZFIwJBEJCBeYTKRgCGFSMCQSiRgSCMSMKQTCRgyiARDQSEAQ8kawOxSOVcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzMrMDI6MDAmURZLAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjMzKzAyOjAwVwyu9wAAAABJRU5ErkJggg==", "name": {"common": "Rwanda", "deu": "Ruanda", "fra": "Rwanda", "hrv": "Ruanda", "ita": "Ruanda", "jpn": "ルワンダ", "nld": "Rwanda", "por": "Ruanda", "rus": "Руанда", "spa": "Ruanda", "svk": "Rwanda", "fin": "Ruanda", "zho": "卢旺达", "isr": "רואנדה"}}, "RE": {"currency": "EUR", "callingCode": "262", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFACZ/ASeApbLS////+6228xkx8xgw8anT+QAAAAFiS0dEAxEMTPIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAaSURBVCjPY2AAAyFjMHBNAwOGUcFRQVoKAgDqd3XHPs6NlAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1MSswMjowMHehDuUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTErMDI6MDAG/LZZAAAAAElFTkSuQmCC", "name": {"common": "Réunion", "deu": "Réunion", "fra": "Réunion", "hrv": "Réunion", "ita": "Riunione", "jpn": "レユニオン", "nld": "Réunion", "por": "Reunião", "rus": "Реюньон", "spa": "Reunión", "svk": "Réunion", "fin": "Réunion", "zho": "留尼旺岛", "isr": "ראוניון"}}, "BL": {"currency": "EUR", "callingCode": "590", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFACZ/ASeApbLS////+6228xkx8xgw8anT+QAAAAFiS0dEAxEMTPIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAaSURBVCjPY2AAAyFjMHBNAwOGUcFRQVoKAgDqd3XHPs6NlAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1NCswMjowMCWZIUIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTQrMDI6MDBUxJn+AAAAAElFTkSuQmCC", "name": {"common": "Saint Barthélemy", "deu": "Saint-Barthélemy", "fra": "Saint-Barthélemy", "hrv": "Saint Barthélemy", "ita": "Antille Francesi", "jpn": "サン・バルテルミー", "nld": "Saint Barthélemy", "por": "São Bartolomeu", "rus": "Сен-Бартелеми", "spa": "San Bartolomé", "svk": "Svätý Bartolomej", "fin": "Saint-Barthélemy", "zho": "圣巴泰勒米", "isr": "סנט ברתולומיאו"}}, "KN": {"currency": "XCD", "callingCode": "1869", "flag": "data:image/png;base64,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", "name": {"common": "Saint Kitts and Nevis", "deu": "Saint Christopher und Nevis", "fra": "Saint-Christophe-et-Niévès", "hrv": "Sveti Kristof i Nevis", "ita": "Saint Kitts e Nevis", "jpn": "セントクリストファー・ネイビス", "nld": "Saint Kitts en Nevis", "por": "São Cristóvão e Nevis", "rus": "Сент-Китс и Невис", "spa": "San Cristóbal y Nieves", "svk": "Svätý Krištof a Nevis", "fin": "Saint Kitts ja Nevis", "zho": "圣基茨和尼维斯", "isr": "סנט קיטס ונוויס"}}, "LC": {"currency": "XCD", "callingCode": "1758", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAApRQTFRFZc//ZM//adD/aND/Z9D/Wcv/q+X/m+D/Wsz/YM7/6Pf+1fH+XMz/Xc3/idr////++/3+e9b/YM3/Zs//Wcr90Pn/xb+9083Kv/P/WMr9Ys7/cND8////S0pJZ2VjZs37rur/7OXiBQYHFhgY+fPwneP/Wsv+YMv78v//j4qIAAAApaCd5v//XMr8XMz+jdz///37KissOzs7gdf+Xs3/Wcn81fv/v7i1AgICAQEBz8jEyfb/WMn9Yc7/ctH8VVRUAwMDBAQEZmNia8/8suz/5t/bBwgJAwMBDhAR7+jkqOj/Wcr+Ycv79P//h4OBBQUEAQEAk4+M7///Xsr7kN7//vr3JSUmAAACf2cAaFQALC0u//38itv/Xcz+2P3/t7GuTT4A/9oA9s4ANywA0/v/WMn8dNL9UU9NLCQD7scA/88A/9MA4LsAHhgDVlRSctH9tu7/4trWAQMGDQkA0q4B/9cA/MsAwqEBBAIABQYI5N3Ys+3/Ysz79///gX16sJEE/9wA+8oA/84A/90AoYUEhIB89v//Ysv7W8v+k9///vn0ExYhemIA/98C+8sAblgA3///n5ynRzgA/9sD/MwA/s0A/c0A/9kDPzEAnpul4P//edX9+P3/b2VG5b0A/9QD/swA47wAZ15DfNb9Wcr8tu3/7ODQ0rAC/cwB/csAz60A59zMuu//ZM/+Z8/96Pj//91L/M8A/s0D/9xJ7Pn/adD9ZM7+W8v9keH//+d5/M4H/c4E/+Zzl+P/Wsv9Zs/8YM//1d+S/c4C/80A29+JYtD/Zc/9adD+n8+jos6Zoc+eoc+doM+doM+en8+en8+fn8+gns+gns+hnc+jnc6gnM+matD9W8//Ws//Z8/8aM/7Z8/7jewN2gAAAAFiS0dEHJwEQQcAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGtSURBVDjLY2CgADAyEqmOiZkolSysbOwcLMSo5OTi5iFCIQsvH7+AoJAwYYUiomLiEpLMhNQJS0nLyMrJyBP0DouIgqKSsoqqGkFXqmtoamlp6+gScCSLnr6BoZaWkYyxCX6VLKZm5loWllpW1jZ47Ra2tZOx13Jw1HKScZbCZySzpIurm5a7u5aHp5c3XiN9fP20/AMC/LUCg4LxmMiiFxIaphUeERmuFRUdE4vbSBbTuHit8ITEpORwLfOUVJwKhW3TZNK1MjKzsnNytfJk8m1xWc4sWVBYVFxSWlaWWF5RWVWNO8Jrauu06hsam5oam1u0Wtvacbmwo7Oru6cXqK6pqa+3v7urswO7K1lMJ0ycNHkKSF3T1GnTZ8ycZYpVobDQ7Dlz582fClbYtGD+wkVzFmNNv8xLli5bXrqiCQpWlq5avWYJNu+sXbd+w8ZNTXCwaePmLVu3YXHh9h07G3ftRgK7Gvfs3Yfpyv0HDjYdakIBhw4fOXoMQyHj8RMnT50+c/bsufPnz1+4ePHSpUuXr1y9hiXvMF6/cR0DYM1jN2/BwG0ouHUTIQsAhaG2QzAXjm0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzErMDI6MDCxzgdiAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjMxKzAyOjAwwJO/3gAAAABJRU5ErkJggg==", "name": {"common": "Saint Lucia", "deu": "Saint Lucia", "fra": "Sainte-Lucie", "hrv": "Sveta Lucija", "ita": "Santa Lucia", "jpn": "セントルシア", "nld": "Saint Lucia", "por": "Santa Lúcia", "rus": "Сент-Люсия", "spa": "Santa Lucía", "svk": "Svätá Lucia", "fin": "Saint Lucia", "zho": "圣卢西亚", "isr": "סנט לוסיה"}}, "MF": {"currency": "EUR", "callingCode": "590", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAkxQTFRF+LC270RS+83R8Vtn/enr9ICK+Kmv7z9N+87S+Kmw/+vr73yI5er1aXm8xMznO1aumKbUGzqg5ej3aHzAxMzjPFaumabV5ej1aH3C5ej0w8vmO1atoK3YIT6i7SY27Sk5//7+7Sg4////7S8+7Sc3/vj5///8/v/v///5/fv88unZ7eB/59JR5Mx58OXK+/j4/vn6/+Hh+9XQ7rZP3ogq0Hg/13sz6qo288ez/tzc//v795KSxFxOzWRBxmRWzl1Ly2NY02FL02Vl8XV3//j496ytgo1eWrRdaLKziJWbe7LAisLSmKm16JGU///+/ry9oI6Rab20oL/ExMTEq7/DirzGmqGs8Zia7yk4/v7y/P/Z/La0ppKemre+vLu7s7OzuLe3pbW5k6i18ZSX/f7g/f3n0ipF0ChE+vmu9PZb85qVlaOxnK2xp6amoaGhoKCgpausirPB4YKJ9+549veCHSWLHCSKHSSK8vaB6u5+8IOHj6i2nq+ywMHB6+vrx8nIqbCwh7nF2nF57d6e4O9h/v74/v//ASKVACOW6/Wo3PFu/JiTylZduYyQtJ+kqre+r6SqupSZvmZt9Wxw5+yM4PF69/j8ACCUACOV+fvi1e1T6up+/KyT+IiG8nN54mBo7m929oOE+5qM8dyQ2e9F9fq+CCmYACGU9fnE2+9a6vc8+PtX+PiW8+Of+PSe9/ps3vNK1+9P7/ekASGUACKV/P3x9fag9/dC8fcy1e5D6fU2+fk38fWD9Pbh/v73/f3q/P3i///1/v7/ACCTlZF16QAAAB50Uk5T/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+RRzUyQAAAAFiS0dEIl1lXKwAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFlSURBVDjLjdBjcwNhFIbhrc3Uzlvbtp3aRmrbbmpra9u2/1izadJ2muy++3y+Zu45B0H4tLTJDNHhF6BSyUAABIV09UhBoC8sAs9jEABReP4bksizITzPgdA8GxoYGkHyLGhsYmpmbmFpJUaQx6C1ja2dvYOjk7MLQR6Drm7uHp5e3j6+fgR5DPoHBAYFh4SGhdMIrsdgRGRUdExsXHwC5/pEHkNAUnJKalp6RmZWdk4uHZPiEnn53ENAQWFRcUlpWXlFZVV1DQYlpWrr6rmGgIbGpuaW1rb2js6u7p5ewJCW6evnMQQMDA4Nj4yOjU9MTk3P0GZl5eZQXkPA/MLi0vLK6tr6xubWtjxlZxfFgWBv/+Dw6Pjk9Oz8QkHx8grFheD65vbu/uHx6VkJJ8uBALy8vtHflVXwsr+QOYY0fvYvxL32H1SlEGV/IPPJhFkOhGbZEJ5lwQ81dWiWBTU0P0kwFP0C9tSUbH0o9ugAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzIrMDI6MDCAJh3/AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjMyKzAyOjAw8XulQwAAAABJRU5ErkJggg==", "name": {"common": "Saint Martin", "deu": "Saint Martin", "fra": "Saint-Martin", "hrv": "Sveti Martin", "ita": "Saint Martin", "jpn": "サン・マルタン(フランス領)", "nld": "Saint-Martin", "por": "São Martinho", "rus": "Сен-Мартен", "spa": "Saint Martin", "svk": "Svätý Martin", "fin": "Saint-Martin", "zho": "圣马丁", "isr": "סן מרטן"}}, "PM": {"currency": "EUR", "callingCode": "508", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFACZ/ASeApbLS////+6228xkx8xgw8anT+QAAAAFiS0dEAxEMTPIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAaSURBVCjPY2AAAyFjMHBNAwOGUcFRQVoKAgDqd3XHPs6NlAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo0OSswMjowMIjkQBwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NDkrMDI6MDD5ufigAAAAAElFTkSuQmCC", "name": {"common": "Saint Pierre and Miquelon", "deu": "Saint-Pierre und Miquelon", "fra": "Saint-Pierre-et-Miquelon", "hrv": "Sveti Petar i Mikelon", "ita": "Saint-Pierre e Miquelon", "jpn": "サンピエール島・ミクロン島", "nld": "Saint Pierre en Miquelon", "por": "Saint-Pierre e Miquelon", "rus": "Сен-Пьер и Микелон", "spa": "San Pedro y Miquelón", "svk": "Saint Pierre a Miquelon", "fin": "Saint-Pierre ja Miquelon", "zho": "圣皮埃尔和密克隆", "isr": "סנט פייר ומיקלון"}}, "VC": {"currency": "XCD", "callingCode": "1784", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAW5QTFRFAFiqBFqnAE61mLhA//sA8O8D9PEA8fAA//cAotAAC5QAHJsAGZoA8/EA8vAA8/AA9/IA+/QA+/UA9vIA/fUA0OMApNEA//YAn9AA1uUAWbMAF5kA5uwA+PMA/PQA1+YAcL0A//gA8PAAqNMAD5YADJUAcb0A/PUAULAADZUAGJkAyOAA//UA7+4D4uoAKqEAFJgAG5sAFZgAxt8A+vQAmM0ACpQAIJ0AT7AA+vMA7e0DfMEABpIAHpwA5+wAKaAAGpoAHZwAttgA7u8A7u4DGJoADpYAptIAdL4AH5wAEpcAN6YA6u0A8O8AfcIAEJcAU7EAutoAzuIAK6EAFpkAsNYA7e4AL6MAEJYA1OQATa8A//wAAI4AXLUAnc8A//8Ae8EAEZcAsdYA//kAV7MA/vUAx98AOqcA8vEASa0AB5MAk8sAjcgAyuAA9fEA9/MAQKkAgMMAvtwACJMAMqQA7e8AYbcA7O4A9fIA////84ExSQAAAAFiS0dEeaHc1NAAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEqSURBVDjLY2CAAEYmZhZWNjTAzsHJxc0DAQzDWSEvH0QNHy9+hfwCgmCVfELCvPgU8omIiomzA1WIS0gK8uJRyC4uJS0jy8snJ8+loKiEWyE7h7KKqpqiEq+6hqaWtg4fLoW6snr6BoZGxiY6pmbmmhaWvEo4FFpxWNvYcnNJidvZGzg4cjmJO+NQ6CKnzePK7ebOIe5h5unl7SPsi9ONin6q/gEsgUFswSFuoRzsOD3DKxwWHhEpoh8V7R4TK86HL3gU4zziE7gSk5JTtFP58EYhu2CakWFiOl8Gobjmk8vU5LbNyubwJaCQXTyHy9HVMBcc5XhTT15+gQFXoSI7wfTILl5UXFIKSjuEEi47R1kcxEACCnmFy9nziMoKvBXE5hneQZ5dcSoEAMJHPXcKhncRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjMxKzAyOjAwsc4HYgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozMSswMjowMMCTv94AAAAASUVORK5CYII=", "name": {"common": "Saint Vincent and the Grenadines", "deu": "Saint Vincent und die Grenadinen", "fra": "Saint-Vincent-et-les-Grenadines", "hrv": "Sveti Vincent i Grenadini", "ita": "Saint Vincent e Grenadine", "jpn": "セントビンセントおよびグレナディーン諸島", "nld": "Saint Vincent en de Grenadines", "por": "São Vincente e Granadinas", "rus": "Сент-Винсент и Гренадины", "spa": "San Vicente y Granadinas", "svk": "Svätý Vincent a Grenadíny", "fin": "Saint Vincent ja Grenadiinit", "zho": "圣文森特和格林纳丁斯", "isr": "סנט וינסנט והגרנדינים"}}, "WS": {"currency": "WST", "callingCode": "685", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAPBQTFRFAABrAABsAABoBgZmAABpAQFrBABpAAByWgBF/wAA+wABBQVuAABnLS2Ft7fVFRV3AgJrAABqAgJjAABjICB+ioq7DQ1zCwttBARtcnKtJyeCAABlBARjAABiVFScsLDRBgZutbXUPT2OAABkBwdwISF9RUWTAgJmAQFpbGyqLy+DAABeAgJsAgJoDw9sEhJzCgpxAgBqBQJrS0mUz83iJyV+BAJrBgBoAABxWwBF/AABAABwAgJxNDqRq7jaFxyAAQFwBABuAAB3WQBI+wACMQBWMgBWMAJVJQJPMgJXNABVJABcegA4/wMA/gAA////43SQlwAAAAFiS0dET25mQUkAAAAJcEhZcwAAAEgAAABIAEbJaz4AAACnSURBVDjLY2CAAEYmZhZGVhCLjZ2Dk4sTA0DVcfPw8vEzCRBUKCgkLCIqxiIuiF8hq6CEpJS0jKycPBMhhQqKSsoqPKpqLATdqK4hIayppa1DUKEAg66evgFeXxuCgZG0sYmptBmIaW5hyWmFRaE1GNiw2NrZMzqAmI5OzpwuWBS6QoCbu4cnhOXl7YPVRDjLlxMvYPCDAU4EC6tCTiLBqMJRhUNSIQD8STwfK4h9AgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNToyNCswMjowMMCeQ2UAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MjQrMDI6MDCxw/vZAAAAAElFTkSuQmCC", "name": {"common": "Samoa", "deu": "Samoa", "fra": "Samoa", "hrv": "Samoa", "ita": "Samoa", "jpn": "サモア", "nld": "Samoa", "por": "Samoa", "rus": "Самоа", "spa": "Samoa", "fin": "Samoa", "zho": "萨摩亚", "isr": "סמואה"}}, "SM": {"currency": "EUR", "callingCode": "378", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABFtJREFUSMftlltsFGUUx8/5Zmane9/tWpbtIi1tKbfSBKmpNIRLAom+KBhFDTEGNSZqjAkmxj7ZxBjffdCEeIkvBtRoAKWCAQtiMDUFhZRq7YW2u5Xt3juzc585PpSQ+MBMKxge9GTe5vvO//ud+c85HxIR3I1gd0X1f+Elxm2Zg1+yGhEAIQABAyCEBXMiIv67xIiIQICsNP2hUhkCZIiwVNWlEhMAZmYmpPKwP9kmSod1yhY0US9mkvf2RKNRIlr8CZZEjOWKlJ892dLaGj7XX5p8QDtfTVybSTXFp8dPEQCAc+dLTUREdigSFgzMb3/c2PcaDQ06Xx2Vex4uvNobSjQhABHQoh2Hi+5cZDv2mcvfty/r0I5tFxJ/gD+NnOXkcnrgmerut2raRFf9ppgYISAE74JzfX19nqyIWJGqX/z8+VOHntsdKybDP5bBlPiAbNuCqPCoflqZeVceEAx7fbhN5MXFaPMevAQEgACvfPTSl4PHNzRhWvlgInRgNrRNZgIAS1qT66zj+6uHJefAEemYBc7BNc/Swh53YY8FCIqu6EptWpvavnLLm5tr1yJ1avuhkmKtCsCoDBDkf6g9uTOzdqeSn8L6/rmz+9IPrQilPB3OV0x3ZuJZsPfkO+VKbl71j+TG7OhBs6RWjdqcVMubkWWaPGHFNurpUXFMCmxO2f4/tXysLmUSuSPxPWfnXSsNPGBuSjWqmWBgxRW2tdvIYtAPnNkg8hWFTwTjaU11NHVY2BPPjxgh6eUr56vWGg4V91+GLxiuxOQAF9AT99P86uWs9dSsuadx9OylI2aiQ9dmpUDzULbQzC4WKfSr2boiOqlwHVllnwE63jj2rYUF94ogAppccEOOddYHZy4Vp85fT21K9l3Wn2Dst3i1TeML6zKH32vc2670Mx1nEp0EPh86nu5iBOD6IDk2RtLBePtQpbirpelo+HUyoFXqF3gtalzcmj32bXRX0V9XHwtmY90N9JMIGQcEAnLP7NG5CMjhfKSV9LGvy7NXTw8PNlx5/2PnReLLAVXhI4ULka4T8oNpeWQ8JPvDAzreo1ILoelJzIX3v+H2mihgqRWsW5vq3NsUzwgd6fXPv9CSHB1llAFTbpzjdjy6cc1n8n1bQvzGupWD5W6Z4hFHMZjP4xumTpRvjQsc0Or81S3J4Pp4qKu5uVgoLI+wzOTE2xdyj4S/scLNv5Q29O5olsQGxYJgIPDdeDZTkwZqgUKihTkm3ZrbfSyiQPa4r2GbnzKm8/Tp610xcoCrqI2P9Zy7jN1N3O+CUto93rMqbEwqIJvzn7TyZ5S4KvqQbHKtthvxQtiM5yzDAhQFXrWJCHyck6DCtLUqyOQEN5ejlO3YIkNEsE2D4wQbGUe2e1pvYQRygDEEutGMiAAt4EXQbeAsEgQ0blqJEIEW7kNeQwK8ggARbgzPhesVAAhg2sABgIDG3zSIFrZ4pv2Ht8ybqRejcSeFbz/+e8J/AeDSLKySWtQHAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjI2KzAyOjAwuMM5cgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDoyNiswMjowMMmegc4AAAAASUVORK5CYII=", "name": {"common": "San Marino", "deu": "San Marino", "fra": "Saint-Marin", "hrv": "San Marino", "ita": "San Marino", "jpn": "サンマリノ", "nld": "San Marino", "por": "San Marino", "rus": "Сан-Марино", "spa": "San Marino", "svk": "San Maríno", "fin": "San Marino", "zho": "圣马力诺", "isr": "סן מרינו"}}, "SA": {"currency": "SAR", "callingCode": "966", "flag": "data:image/png;base64,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", "name": {"common": "Saudi Arabia", "deu": "Saudi-Arabien", "fra": "Arabie Saoudite", "hrv": "Saudijska Arabija", "ita": "Arabia Saudita", "jpn": "サウジアラビア", "nld": "Saoedi-Arabië", "por": "Arábia Saudita", "rus": "Саудовская Аравия", "spa": "Arabia Saudí", "svk": "Saudská Arábia", "fin": "Saudi-Arabia", "zho": "沙特阿拉伯", "isr": "ערב הסעודית"}}, "SN": {"currency": "XOF", "callingCode": "221", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAM9QTFRFqs4N56QA6KQAC3ImDHMm//8AvAEAvAAA6PIE4e0Fp8wOksAR/f4AY6UYT5kb+PsB5O8ELYYhI4Ai2+oG4u4Fqs4NrdANgbYUEHUlDnQme7MVqM0O1OoH7vUDdK8WFHclDHImDnQlXqIZ3/AFncYPJIEiCnEmCnImGnskhrkT8fcCnscPDnMmCXEmf7UUB3AnBW8nZ6cY8/gCRJMdHHwjkcARoMgPJYEi5fAEzuMIUZobv9oK/v4AyeAITJcct9UL4O0F4u4Ez+MH/v8A////g18ZmAAAAAN0Uk5T/f7+l1A3dQAAAAFiS0dERPm0mMEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAACOSURBVDjLY2BGAiwMrEiAkY0dCTCMKqSZQg5OIhVycROnkIeXj58ohQKCQsIEFYqIioqJS0hKiYlKy+BVKCsnz6wAYioqKTPhtZpfRVWNmVldQ1OLoBu1ddSZdfV4CHtGU19SwsCQsEIjYxNTM3NBC4IKLa2sbVht7ewJKnQQAYWhoxOxicJ5kKfHIasQALWVHsM/YfmTAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjAzKzAyOjAwRxx6lgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowMyswMjowMDZBwioAAAAASUVORK5CYII=", "name": {"common": "Senegal", "deu": "Senegal", "fra": "Sénégal", "hrv": "Senegal", "ita": "Senegal", "jpn": "セネガル", "nld": "Senegal", "por": "Senegal", "rus": "Сенегал", "spa": "Senegal", "svk": "Senegal", "fin": "Senegal", "zho": "塞内加尔", "isr": "סנגל"}}, "RS": {"currency": "RSD", "callingCode": "381", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABFhJREFUSMe9ll1sFFUUx8+5987O7sxOu91lC8uq9IuS0ihpJRqlBQNWjQajLwY1+ABoDCYajTEmvsiDr774wJMx8cHwZIxRHwgiImBEHviKfKwSKdCPbbs7ne7OzM7MvccH0Zhti3UK/nOf7tyZX/5nzv/k4okHhmCJQgBa6tl/FVsikhRShID/GxgRGFJE3ApFNqCQgCGw28C/JRgBfJ+8QOfJKxn9fDaR0JLUCKHuAxDgsvBi0SdEIIF6e1Rnu6t8ZQHTeL0Yct1kozNYugpRBIIDxfztizkmYFxu7m8MtcPdER04sh7swWwDDhzB1GyweWW0pQ9MA6S8E6UWkCf21cmgMQEdqyqzdnniOiuu9lM2fvEjWD6IRGy7AIDHF4wTIo/IyZHTv0KJq62Ql4EjwU9pxRkcFao7dbmSu0GRFj9gQiz4KpHiZI5hVLzeNiyFO9ZATRFL0VXLZJXTo+nfDZUiQfH7S9ioNbsFUEQpXbO4vHFNMF+0lMv2CrNBvFCteixXvY75pPJSiboXYNzexsKWt5q2OJCN2pvJ6RfNOX9NZwAnxeBz7Ogpcmsw8mhY+jzh9OiV2uFy4z25xowaKtZYWcAxB6pxveb5HR+8/duhn8rHPlmzM2UUdgflKfGQefngmWyuv3vfG9/sfd8mEaKKB2YCqHkhgOeZa3v0wUGzfy0/m5378Ou2gb7C9hFn/5fwrZZev17r6mkZHEC3Lhgu8IWlrPmtRYggI766UJ+eth7bVnh1tzPnVhs+RmFjZW/+hR35l56v2TOsWCB1DQApVmcvlmMkUoTgVqu4Zag6eaP63fdzJ09Nnz+D2x5x3bpSkm6GOGaeFh+ZkdS4SGiJyJ5FzzcMQzdNFkbRTEVL6BpEGMnljOtFwIxFruu6dSmU2d3V98qeto4Oput9e3YFuaz0PB9lUKshYuzRJeYHEQGAM3A9nXG0WuZKpakfjnEhNNMcP3bCGNjQ1tWVbNShXkfGECBelEUYNQ96BQTEw6lp6ThRMj1X+tWenMyNT1AmY4+PyUxLsGFDgqtgfEIBC0Op4jnOZ8zmMgMkmCaUwwEr5y5m7h8IPc8ZHU0FjUQ2u+qJkZnT51o33suCIJdrT0NMMFYct3lPKZ42naNH7Ytn7SuHo/aieQmSu3Zga4v30cd+p5DlX3Ldj1vFruxT22W9BozHcdxmpZr3iADBHNh46dN3k0VbP3Ee0sP5detQ18atdHDos8Sm1rHj+4f3HTQEkGUgW9rFramuNF8ASkqeMfqefse/oPjDYD45zLnGQmluHdK2GfWS07PpdaPnHiUlIFIsMVxIjHMiWv3sM11bX7OPcKuzX0vpQtOs7l7nlFG8b2f3y3sBgHGOsUWL3yKIFCKzj/9cKV2QSqGmccEzd3W2bXoQcLmXvVuBAYCUQsZIysieBcZEi4Wck1KIeGfBN9l/Y4iIKF43/WfwX3wCgGW6/KfEUg/ePuSfug1Fi6c/ADM9TeJi4wSsAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjUzKzAyOjAw4D4fzAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo1MyswMjowMJFjp3AAAAAASUVORK5CYII=", "name": {"common": "Serbia", "deu": "Serbien", "fra": "Serbie", "hrv": "Srbija", "ita": "Serbia", "jpn": "セルビア", "nld": "Servië", "por": "Sérvia", "rus": "Сербия", "spa": "Serbia", "svk": "Srbsko", "fin": "Serbia", "zho": "塞尔维亚", "isr": "סרביה"}}, "SC": {"currency": "SCR", "callingCode": "248", "flag": "data:image/png;base64,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", "name": {"common": "Seychelles", "deu": "Seychellen", "fra": "Seychelles", "hrv": "Sejšeli", "ita": "Seychelles", "jpn": "セーシェル", "nld": "Seychellen", "por": "Seicheles", "rus": "Сейшельские Острова", "spa": "Seychelles", "svk": "Seychely", "fin": "Seychellit", "zho": "塞舌尔", "isr": "איי סיישל"}}, "SL": {"currency": "SLL", "callingCode": "232", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFGNIY2vja5OT6IiLUAM0A////AADNWt8AqwAAAAR0Uk5T/v7+/rpKSvwAAAABYktHRAX4b+nHAAAACXBIWXMAAABIAAAASABGyWs+AAAAJ0lEQVQoz2NwwQIYhoogViCIBTCEYgGDUVAJC2AwxgIY0rCAoSIIAG33tKEi+b2bAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjM0KzAyOjAw4/YoxQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozNCswMjowMJKrkHkAAAAASUVORK5CYII=", "name": {"common": "Sierra Leone", "deu": "Sierra Leone", "fra": "Sierra Leone", "hrv": "Sijera Leone", "ita": "Sierra Leone", "jpn": "シエラレオネ", "nld": "Sierra Leone", "por": "Serra Leoa", "rus": "Сьерра-Леоне", "spa": "Sierra Leone", "svk": "Sierra Leone", "fin": "Sierra Leone", "zho": "塞拉利昂", "isr": "סיירה לאונה"}}, "SG": {"currency": "SGD", "callingCode": "65", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAARFQTFRF3wAA3wMD4AUF3wEB3wIC3gAA3AAA3QAA5jc38piY9rW16U1N3gEB3wUF85yc/////OPj6EVF5TQ04hcX8pub/OXl4h4e4hYW/Onp74CA5j097XJy4yUl4A0N3wQE6U9P5Cws4AgI4AQE8IuL/vj44yMj4AkJ+tfX8IeH3gQE5Cgo/Ofn62Fh9Kur/Obm4A4O7W5u50VF4AYG4RIS7nh45S8v852d/e7u4hkZ3gsL3wcH3xAQ3AEB615e6FBQ4QwM86Oj74KC5S4u+MfH+dDQ97+/856e7nt75CUl5jU15C0t86Gh3QIC4AMD8ZKS/fHx+MbG6ERE4x8f4hoa4RER4RMT4hUV4RAQ4AoK/e/v//7+yK8ssAAAAAFiS0dEDxi6ANkAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADkSURBVDjL7dDHUgNBDEXR16jfCEyQTQ5mwASDyXlMNDlnTDD//yFAsZ72bKnirG9JKgE/XItDBuIZKeClaaetbbl2dLBJh84uyxe8dPfAh0KvvdbXH3FgcEhdYLvjsNlIVByNx8ZZLKWHEzppU9PlmVlWdG5+ganbnS7a0jJWVrmG9Y3N9DDWLUuq0O0dcHcvcGOs+2YHWjs84vFJgemloHRqZ+T5hV7mrhh4kOd13m5cVIG/Df1R5I7V+4fHJxGJJR1IgpE8vzAM9W+vb+/lj3oYkl+NRhIGywifGWWf+B/+9fALahtTyExnkYkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDcrMDI6MDCzU16FAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjA3KzAyOjAwwg7mOQAAAABJRU5ErkJggg==", "name": {"common": "Singapore", "deu": "Singapur", "fra": "Singapour", "hrv": "Singapur", "ita": "Singapore", "jpn": "シンガポール", "nld": "Singapore", "por": "Singapura", "rus": "Сингапур", "spa": "Singapur", "svk": "Singapur", "fin": "Singapore", "isr": "סינגפור"}}, "SX": {"currency": "ANG", "callingCode": "1721", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAk9QTFRF+LC270RS+83R8Vtn/enr9ICK+Kmv7z9N+87S+Kmw/+vr73yI5er1aXm8xMznO1aumKbUGzqg5ej3aHzAxMzjPFaumKbV5ej1aH3C5ej0w8vmO1atoK3YIT6i7SY27Sk5//7+7Sg4////7S8+7Sc3/vj5///8/v/v///5/fv88unZ7N9/59JR48x58OXK+/j4/vn6/+Hh+9XQ7bZP3ogq0Hg/13sz6qo188ez/tzc//v795KSxFxOzWRBxmRWzVxKy2NY02BL02Vl8XV3//j496ytgo1eW7VdZrCxgo+VebC9iMHQlqiz6JGU///+/ry9oI6RabyznbvBwcDAqLzAiLnEmKCr8Zia7yk4/v7y/P/Z/La0ppKelrO6uLe3rq6utLOzobG0k6e18ZSX/f7g/f3n0ipF0ChE+fmu9PVb85qVlKKwl6isoaCgm5ubmpqaoKanibK/4YKJ9u149vaCHSWLHCSKHSSK8faB6u5+8IOHjqe1maqtvL296urqxMXFpaurhrfD2nF57d2e3+5h/v74/v//ASKVACOW6vWo2/Fu/JiTyVVct4mOsZyiqLW8raGnuJKWvWRr9Wxw5+yM4PF69/j8ACCUACOV+fvi1e1T6ep9/KyT+IiG8nN54mBo7m929oOE+5qM8NuP2e9E9Pq+CCmYACGU9fnE2u9a6vc8+PpX+PeW8+Of+PSe9/ps3vJK1+9P7/ejASGUACKV/P3x9Paf9/ZC8Pcy1e5D6fQ1+Pg38fSD9Pbh/v73/f3q/Pzi/Pzn///1/v7/ACCTZ/3rWQAAAB50Uk5T/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+RRzUyQAAAAFiS0dEIl1lXKwAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFkSURBVDjLjdBjcwNhFIbhrc3Uzlvbtp3atm07tbW1bdv9Yc2mSdtpsvvu8/maueccBOHR0CQzRIuXj0olAwHgF9DWIQWBrqAQPI9BAITh+W9IIs+C8DwbQvMsqKdvAMkzoaGRsYmpmbmFCEEeg5ZW1ja2dvYOjk4EeQw6u7i6uXt4enn7EOQx6OvnHxAYFBwSSiO4HoNh4RGRUdExsXHs6+O5DAEJiUnJKalp6RmZWdk5mBQVy83jHALyCwqLiktKy8orKquqMSguUVNbxzEE1Dc0NjW3tLa1d3R2dfcAuqRUbx+XIaB/YHBoeGR0bHxicmqaNiMtM4tyGwLm5hcWl5ZXVtfWNza3ZCnbOygOBLt7+weHR8cnp2fncvIXlyguBFfXN7d39w+PTwo4WTYE4Pnl9e1dUQkv+wsZo0viZ/9C3Gv/QWUKUfYHMp5MmGVDaJYF4Vkm/FBRhWaZUE39kwRD0S96IZTLS03RjwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo0MSswMjowMLsLDnsAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NDErMDI6MDDKVrbHAAAAAElFTkSuQmCC", "name": {"common": "Sint Maarten", "deu": "Sint Maarten", "fra": "Saint-Martin", "ita": "Sint Maarten", "jpn": "シント・マールテン", "nld": "Sint Maarten", "por": "São Martinho", "rus": "Синт-Мартен", "spa": "Sint Maarten", "svk": "Svätý Martin", "fin": "Sint Maarten", "zho": "圣马丁岛", "isr": "סנט מארטן"}}, "SK": {"currency": "EUR", "callingCode": "421", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAr5QTFRF//////7+//v7//z8//39/v7+/v39/v///+7u/3t7/nh4/nl5/nd3/4aG/4+P/4mJ/np6/nBw/87O/vz8/97e/gUF/gAA/gEB/gMD/kVF//f3/nx8/gQE/6Gh7u727uz17fT9+dnc/wsL/gYG/goK/iUl/mlp/ggI/aKj7///7eryEhKHFRSHBAyHr4+2/xUO+wAA/5WV/7q6/7e3/6en/8PD/iAg/QAA/wAA6o2XHy+eDwuBExOHAAB8AwJ8AAB7qYiy/xYO+gAA/6mp//T0/+fn/+zs//Ly/iEh6YyWDR6TAAB2AQF8AgJ/BgR/AAB+qYmz/xcP/hMT/i0t/mpq/h4e/hkZ/QEC6YyXDyCWAAB5AwN/AQF+BAN+qIiz/xoS/mJi/+/v//Dw/+Li6IyXDh+VAAB4AgJ+qIm0/xsT/mBg//X1/9nZ/eTl+/7//ezt/9vb/6Sk6I2YBQR/mY/B/y4m+w4P+gkL/ikp/25u/AAA+gUH+gsN+wEC/wEA252vBRKNAAB6BQWAAABzYm+7/2Fa/wQD/gIC4yAtrcLrzVBo/wMD/wIB/ycgqprDBAN/AgF9AAB3IS+b9ZufqAQvnQIypgItDwF3AAF/ggI/tgIlmAU46QAA/3BmXGu5AAByAAGAAwOBsKHGfTxzAACABASIAAGDAQGABAR/AgKAAAGCAwKEAAGKOwti27LAChaOAAB9AQKBEgF1FAN3BwBwPzONp7LeAAJ3BQJ6AgF+BAJ+CQZ/AABseYnLcmyuAQBrFgR47QEK7QEJ7gQN5gAA/2ddlqDVAQB1BQSAAwJ5AABxYnfG/peR6AAA7QMM/wQC/2Nbta7RHSycAAB0AgJzDRiPk5vR/4qF/gIA/zAp86GkkY/GOkyteX/B5qq2/0tD/QQE/QIC/QEB+wEB/zwz/4N+/05G/gUC/QMD/AICh3PFowAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAHCSURBVDjLY2AYdoCRCQyYmcEUC2WGsbKysrFzcHJxc/Pw8vEDKQFBIXZhVgwAUsvCICIqJi4hJiklLSYhJiMmy8CExUQ5OTl5BUUlCWUVMVUGNTFlZXUxDU0tOQzAoK2traOrp28gZmhkzCBkYmpmbmFpZW2jjQ4YbG1t7ewdHJ3EnF1cGVzc3D3MLTy9vH1s0QGDr6+vn39AoFOQh1gwQ4hYaFi4RURkVLQvOmCIiYmJ9Y+Ld0pgdE1kSEp2NzG3SElNS49BBxCFGZlOWdk5uXn5BUmF5hZFuBQWp5WUOpWVG1QwVFZV19TW1Tc0YlfY1NzS6tTWbtHR2WXR3ePU22ffH4OpcMKECbETJ002nzJ12vS0GTNnzZ4zd958vwkYgGEBECy0XbR4ydJly1esXLV6zdp16zdsXIABGDYBweYtW7dt3+E3IWbnrt179u7bf2ATJmA4CASHDh85eqzx+IkVJ06eOn3m7LmDWABDHRicN7hw8dLlK81Xr12vulGHDTCEQ4CMwc1bt+/cvXff4MHDR+FYAIM4FMg8Fnvy9NlzA5l2cawArlBc4oVTldNLGXFxQgqBSkVxKkNViBcMoEIAB2QRibuKCK0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTgrMDI6MDCJsS7yAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjE4KzAyOjAw+OyWTgAAAABJRU5ErkJggg==", "name": {"common": "Slovakia", "deu": "Slowakei", "fra": "Slovaquie", "hrv": "Slovačka", "ita": "Slovacchia", "jpn": "スロバキア", "nld": "Slowakije", "por": "Eslováquia", "rus": "Словакия", "spa": "República Eslovaca", "svk": "Slovensko", "fin": "Slovakia", "zho": "斯洛伐克", "isr": "סלובקיה"}}, "SI": {"currency": "EUR", "callingCode": "386", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAbBQTFRF/////v///f3+/Pz9+/z9/P3+/f7+/v7+/v7///7++vv80drut8PXpLTTq7nSvMje4Obz/f391LbBKE2hADKiNVFwGTeJMUl2HEKCAjmlUV+g+t/d/v3+/Pv8x5CdACKICz+fAjalOliBI0qQBjiiADqhJSd5+c7J/v3916atDiyKCD2gAjScIUeIFUCQBTSbADujPDh//93X/fz9/vz85ra3FCmDCz6jDzycdpLOSW25FT+dADSgTT59/+nh/f//7vL47u/28Pz/68HBGCJ6eJrR5ur07PD34Oby5urzL2C1WD54/+vn6/T97vH3GUejGkaiE0qqMTeEfWmW+/////792+j4YT91IESbF0elGUajBDaaAjedCjCQd055ssvroLDWq7vcobLXqb/iqK/QVjNsADaeBjaaCTqcCzqbAz2jNSt0bFmMyN/3wdHrzt/0rMHjXDdtHzOKBDyhCjqcCDmcCTmcBTuhPjF3j2mN29HcyLfJdk14JzOFAzuiCTmbATuiDyyKOClwLSh0BjKVCjmbBjygATyjAjyjCDueBDmeFziTzyoh4ikW3SkZ3ikYRVLwSwAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEFSURBVDjLY2CgEWBkYmZhYWUjqI6dA0pzElLJxc3Dy8cvQECVIIOQsIiomLiEpJQ0gwwehbIMcvIKikrKKqpq6gwauFRxcHDIMmhqaevo6ukbGBoxGnNgByC1JgymZuYWllbWNrZ29ji94+Dg4Ojk7OLq5u7h6eXt4+vngB0w+Pv7BwQGBYeEMrAxhIVHREb5YwcM0UAQExsXn5CYlJySmpYejQMwZABBZlZ2Tm5efkFhUXFJBg7AUAoCZeUVlVXVNbV19aW4AERhaVlDY1NzS11rKSGFpa1t7R2deNTBFZaWteJTBlRYTyRg6CISMHQTCRh6iAQMvUQChj4iAUM/kWBkKgQAEhil/zRkdXwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDMrMDI6MDBHHHqWAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjAzKzAyOjAwNkHCKgAAAABJRU5ErkJggg==", "name": {"common": "Slovenia", "deu": "Slowenien", "fra": "Slovénie", "hrv": "Slovenija", "ita": "Slovenia", "jpn": "スロベニア", "nld": "Slovenië", "por": "Eslovénia", "rus": "Словения", "spa": "Eslovenia", "svk": "Slovinsko", "fin": "Slovenia", "zho": "斯洛文尼亚", "isr": "סלובניה"}}, "SB": {"currency": "SBD", "callingCode": "677", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAexQTFRFAADWCgrXFRXZBATXGRnaAADVOzvfcnLoHx/ahITrCAjXKirdbW3or6/y2Nj5eXnqSkriBQXXGxvaZWXmmZnv5OT7WFjkDQ3YDg7Yd3fp9vb9////sbHzJyfcBwfXWVnk5+f7zs73OzvgAgLWQEDgx8f2iIjsBgbXAwPWISHbxcX2wsL1q6vxCQnXOjrfLi7dExPZTEziCgrYVFTjKSncPT3gDAzYFhbaAQHWLCzdvr71hYXrOTnfRUXh/v7/9fX9mprvHx/bAADXAADZgoLr7u78ODjfEw/HVkWObm7oW1vlS0viEg7IX0yHxJ0x+cgFERHZHBzabGznHBzbv7/1EQ7IXkuHxJwy+skF/88A/84AKSndpKTw7Oz8+fn+xsb2GBjajIzt4+P6/Pz+1dX4w5wy+ckF+coAz7cA6+v8+/v+Hh7b1NT4/f3/ra3y+ssAx7QAYokAFmkAT0/jfHzqf3/qMjLeiYnsi4vsXUuIw5wzY4oAE2gAAF8ABQXWXUqIwpszYooAAGAAEQ3IXEqJEA3Jwps0XEmJwZs0W0mJwZo0W0mKxrQAwJo0WkiKwJo1wJk1EAzJWkiL+MgFDwzKWUiLv5k1+MgGWUeLv5k2WEeMYYkAx58v98cG/80AxbMA/8wA3r4Ab48AAF4AdyFA2wAAAAFiS0dEGwJg1KQAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGjSURBVDjLhdEFV8MwEADgwAgwdMN1MBgwpLgMhw0fMpzh7u42pLi7O/xRrmVY13T3+l6uyffu2gtCCFlYou8QWSFyYGsb/J3aiu2Izt7B0clZImVSF1c3d7GHJwF6efv4+vmzqSwg0FceFEzqrJCHhMIapkQoPCIyiiL2jo6JjYvHOCERY/ukZEkKEargEaUq0tIzXJhUhYQiODMkKzsnl3iu1rAl4fNxXr57AUxJyc8Ki5i/yVVgRBUXlGhhBFolDystK69ASFdZFRVuJZUhlV1ytbYmpjKMw2rr6hv0jQg1Nbe0tiWwhdo7Oru6i+M5rKdX39jXD52DBgbFxv2h4ZFRypSNjU9MwuvUdMmM7Otkdm5qPuUPW1hk2dKywQAbuhWskxqvk8Je1A9bXWPZOjCaJs5tY3Wt4pcRILDN/4wXqjVbm9scxgOB7eyaMBMIbG+fh3EgsAOWHU5w2D/IsCMC+wOBHZ8Q2Q9k2KkAM0K15uz89EKIsVCtuby6vhFmAIHd3pllAGvvv9iDIAP4+KR/7nsxxwDqX1/f3ic/zDCa/gQIahql9QdxMQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNToxMCswMjowMLpeYJUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MTArMDI6MDDLA9gpAAAAAElFTkSuQmCC", "name": {"common": "Solomon Islands", "deu": "Salomonen", "fra": "Îles Salomon", "hrv": "Solomonski Otoci", "ita": "Isole Salomone", "jpn": "ソロモン諸島", "nld": "Salomonseilanden", "por": "Ilhas Salomão", "rus": "Соломоновы Острова", "spa": "Islas Salomón", "svk": "Salomonove ostrovy", "fin": "Salomonsaaret", "zho": "所罗门群岛", "isr": "איי שלמה"}}, "SO": {"currency": "SOS", "callingCode": "252", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAQJQTFRFQKb/Qab/PqX/P6b/Qqf/NaH/gMT/YbX/OKL/Qaf/1+z/nNH/MJ//Q6f/O6T/V7H/////1ez/PKT/PaX/L57/lc7//f7/Yrb/NKD/PaT/OqP/UK3/Tqz/Rqn/S6v/TKv/Sar/2O3/q9j/Ua7/Taz/Sqv/brz/4PH/+/3/7fb/7vf/8vn/8Pj/8/r/xOT/U6//w+P//v//+v3/mtD/M6D/fsP/6/b/0ur/XbP/MZ//RKj/K5z/dr///P7/9/v/NKH/rdn/gsX/NqH/P6X/5fP/lM3/rNj/wuL/db//zuj/W7P/Mp//cb3//P3/VK//O6P/N6L/s9v/ntL/OaP/tt3/ksz/0MjB+QAAAAFiS0dEEJWyDSwAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADzSURBVDjLY2AYBSQCRmIVMjETpYyFlY2dg5MohVzcPLwEHAcELHz8AoJ8LJxAJm6FQkJMwgwiogJi4hJMkpJSwrjUMUvLyMrJKygKCCixKKuo8qswYVfHyaemrqGppS0mIKChraMroKcvjMN2Tg5lAwFDIAQhI2NhPpx+Z+EzMTWDKDS3sGTAE0acVtY2Ara2tgJ28vZWeAOIl9VBQAPoRgFHJ7xByehs5SJg5+omIODuxILfQA8BTy9vCR8XXz9/fNHIEhAY5BRsxcvKHBJqgs9uZitlE0agnbzCTsp4/cLALAQxh5FFCL9CRKIlOvUORQAA7TsXFkeDmckAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTMrMDI6MDCLtnoIAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjEzKzAyOjAw+uvCtAAAAABJRU5ErkJggg==", "name": {"common": "Somalia", "deu": "Somalia", "fra": "Somalie", "hrv": "Somalija", "ita": "Somalia", "jpn": "ソマリア", "nld": "Somalië", "por": "Somália", "rus": "Сомали", "spa": "Somalia", "svk": "Somálsko", "fin": "Somalia", "zho": "索马里", "isr": "סומליה"}}, "ZA": {"currency": "ZAR", "callingCode": "27", "flag": "data:image/png;base64,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", "name": {"common": "South Africa", "deu": "Republik Südafrika", "fra": "Afrique du Sud", "hrv": "Južnoafrička Republika", "ita": "Sud Africa", "jpn": "南アフリカ", "nld": "Zuid-Afrika", "por": "África do Sul", "rus": "Южно-Африканская Республика", "spa": "República de Sudáfrica", "svk": "Juhoafrická republika", "fin": "Etelä-Afrikka", "zho": "南非", "isr": "דרום אפריקה"}}, "GS": {"currency": "GBP", "callingCode": "500", "flag": "data:image/png;base64,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", "name": {"common": "South Georgia", "deu": "Südgeorgien und die Südlichen Sandwichinseln", "fra": "Géorgie du Sud-et-les Îles Sandwich du Sud", "hrv": "Južna Georgija i otočje Južni Sandwich", "ita": "Georgia del Sud e Isole Sandwich Meridionali", "jpn": "サウスジョージア・サウスサンドウィッチ諸島", "nld": "Zuid-Georgia en Zuidelijke Sandwicheilanden", "por": "Ilhas Geórgia do Sul e Sandwich do Sul", "rus": "Южная Георгия и Южные Сандвичевы острова", "spa": "Islas Georgias del Sur y Sandwich del Sur", "svk": "Južná Georgia a Južné Sandwichove ostrovy", "fin": "Etelä-Georgia ja Eteläiset Sandwichsaaret", "zho": "南乔治亚", "isr": "ג׳ורג׳יה הדרומית ואיי סנדוויץ׳ הדרומיים"}}, "KR": {"currency": "KRW", "callingCode": "82", "flag": "data:image/png;base64,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", "name": {"common": "South Korea", "deu": "Südkorea", "fra": "Corée du Sud", "hrv": "Južna Koreja", "ita": "Corea del Sud", "jpn": "大韓民国", "nld": "Zuid-Korea", "por": "Coreia do Sul", "rus": "Южная Корея", "spa": "Corea del Sur", "svk": "Južná Kórea", "fin": "Etelä-Korea", "zho": "韩国", "isr": "קוריאה הדרומית"}}, "SS": {"currency": "SSP", "callingCode": "211", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAArJQTFRFCzWDAw0hAAAAAAEBAAEDEEq3D0atCCdfAQUMAAECD0ewEE29DkKiBhxGD0evD0asD0izEEy7DDmNBBIsAAABD0euCi91AgkXEEy6CCRaAQQJEEy8DT+cBRk+AQEBD0m0DDeHAw8mEEiuD0auEEu4EEm1CCttCA4ZIR0VExMTDQ4PEQ4OEQ0ODkawC0SyD0u6E0mtepTG6+vt/v337v7+7v//8P/+DUawFUurJlafCUOzEUitEUiuEkmtAj2tNWrDsJ7B+ouK+oqK7o6U8I2S8Y2RsK89JVSgB0K1ATy7EEeuEkmvBkCrADelQSd2sQkk5QAA2gAF0gAL1gAIEUmtFEqsB0G12ckftrM4BUC2OWGSjZZXOGGSB0K0EUivEkqxBE67DEy1YzV1zBop6hIT2xYe2BcgBUC3ADm+CkSy08Mm/+UB1cQl/+oApqpDBkG1EkatAEu6G0Smgilb3hEX5w8Q2RIb2hIaR2uIoKNK3soe/N0J+NsL/+cAyb0tCkOzEkasDUixBEq3KECdmyNJ3xEXR2qIn6JKyb0sKECcmyNI1MMlpqtDHESmgila2coftrI5jJZYDUy1ZDV1sK49EEevBkCsADimsQok2gAG0wEL1gAJJlWfD0itDkavAjqsNWjDsJzA+oiJ+YiK7YuT8IuR8YuQD0ivD0K3E0yue5/J7PTw/v/67/7/7///8f//D0S1EEWzC19+D4ZCKJ5CGpI/FI08F407F406D0WyEEO3DVaSCXtMB44oBYorAIQsAoYsA4csEEK5Dk6hCnFeB4ssB4wqB4gyB4kxCIoxD0iuC2dxB4c0Bo0nB4kwD0S0DF2FCIBABo4mB4ovD0ayEEK4DVSWCXhQB40oDkylCm5jB4otB40pB4gxC2V1B4U3Bo4nDViPCH1IBo8lB4ou////wjbOagAAAAFiS0dE5VhlC78AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAHTSURBVDjLY2BgZGJmYSIMGFjZ2DmYODkJK2Tj4ubh5SSslIGPj19AUEiYSYRThJBCPlF+VlYxcSZmTkIK+fjY2CT4JaXw2g9RCLSfS1pGlgmoVA4HgCkE2i8vqKDIJELIRCU+PmV+FVU1dQ1NLW0dLACqUFdPCeRUPn0DQyNjE1MzTABRaG5haWVtYwtUqmtn7+Do5Oziig4gCnVt3dw9PDy9gExvH18//4DAoGA0AFYYEhoWHhEZFR0Ta8PHFxefkJiUnJKalooCwArTMzKzsnNy8/JtwOYX8BUWFZeUlpUjA5BC3YrKquqa2rp6cyVoGDQ0NjW3tJZhKGxrBynsQFHY2YWpEGJ1d24PwurePmxWgzzTPyEyZiLMM5Mm4/AMMHimgIJnKih4pk33mxEwc9ZsbMEDDPA5kACfO2/+goWLFi9ZugwdoETh3OUrVq5avWbtuvWYAClRiM7dsHHT5i1bt23fgQXAkxnf3J27du/Zu2//gYNYAUzhXK5Dh48cPXb8xEkcAKLw1Kldc0+fOXvi+DmcAJy55m44f+Hipct4lIEVzr1y9dr1G0fxKgMqPMV16Oat23eO3z1HQOGGuffuPzhHwDiQwoePHj8hrOzcOQAzcawWKKBwzgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNTowMCswMjowMHb0YAsAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MDArMDI6MDAHqdi3AAAAAElFTkSuQmCC", "name": {"common": "South Sudan", "deu": "Südsudan", "fra": "Soudan du Sud", "hrv": "Južni Sudan", "ita": "Sudan del sud", "jpn": "南スーダン", "nld": "Zuid-Soedan", "por": "Sudão do Sul", "rus": "Южный Судан", "spa": "Sudán del Sur", "svk": "Južný Sudán", "fin": "Etelä-Sudan", "zho": "南苏丹", "isr": "דרום סודן"}}, "ES": {"currency": "EUR", "callingCode": "34", "flag": "data:image/png;base64,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", "name": {"common": "Spain", "deu": "Spanien", "fra": "Espagne", "hrv": "Španjolska", "ita": "Spagna", "jpn": "スペイン", "nld": "Spanje", "por": "Espanha", "rus": "Испания", "spa": "España", "svk": "Španielsko", "fin": "Espanja", "zho": "西班牙", "isr": "ספרד"}}, "LK": {"currency": "LKR", "callingCode": "94", "flag": "data:image/png;base64,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", "name": {"common": "Sri Lanka", "deu": "Sri Lanka", "fra": "Sri Lanka", "hrv": "Šri Lanka", "ita": "Sri Lanka", "jpn": "スリランカ", "nld": "Sri Lanka", "por": "Sri Lanka", "rus": "Шри-Ланка", "spa": "Sri Lanka", "svk": "Srí Lanka", "fin": "Sri Lanka", "zho": "斯里兰卡", "isr": "סרי לנקה"}}, "SD": {"currency": "SDG", "callingCode": "249", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAMlQTFRFYGAA3hQA/wAAAZoAPHYAxCQA/gAAAJoAAJwAG4oAmD4A9wUAAJsACJYAZ1wA5BAAAZsAO3YAZ1sA5B8P/x8h/x8fO5kjxMik/uHi/+Dh/+DgG6YdmNia9/34////AJkACJ0IZ8Nn5PTkAZkBO7E7xOfE/v/+GaQZnNic/f79PLI8xOjEaMNoHaYdmtia+P34I5kjpcil4uHi4eHh4ODgAJYAAFsADx8PIR8hHx8fAIoAAD4AAAUAAAAAAHYAACQAABAAAGAAABQAPQknRwAAAAFiS0dEHnIKICsAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAC6SURBVDjLrdLXDoJQFERRFPVYsPcKir33fm3//1ESIIgGcubBeV7Jzi2SFAhCk+RQOAJBisbiCQiSkkylIUiUyfJ5EyJ5CwJ5G/J5B3L5DzTyuXyh6DcXJJJL5Uq15r0vSFRvNFve+4Gq1u5AkEjv9voQJHUwHP0V6uMJkla1KXaY2XwBXc9ytd5sved+wt3+cDz5zfUpzper8J8DldtdCB4a2YcAIJN1IJe1IZ+1IJC1IJA14fMFOfEGHmN+xeZewuYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDYrMDI6MDB+rDD1AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQ2KzAyOjAwD/GISQAAAABJRU5ErkJggg==", "name": {"common": "Sudan", "deu": "Sudan", "fra": "Soudan", "hrv": "Sudan", "ita": "Sudan", "jpn": "スーダン", "nld": "Soedan", "por": "Sudão", "rus": "Судан", "spa": "Sudán", "svk": "Sudán", "fin": "Sudan", "zho": "苏丹", "isr": "סודן"}}, "SR": {"currency": "SRD", "callingCode": "597", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAPxQTFRFAJkAAZkBAJcAEaAR7vfs/////uvq/ujq/uzq/xAQ/w8Q/yIQ/wAA/wEA/xEA/58A/wMA/wEB/wMB/wQB/wcB/wAB/0YB//8B/zAB/wIB/5YA//8A/4EA/wQA/ygA/1MA/0AA/0EA/0oA/+MA/9kA/0cA/1sA/yQA/xoA/7EA//4A/6AA/1IA/+gA//0A/98A/0IA/wUA/ywA//sA//wA//QA/yAA/1QA/0sA/6wB/6EB/ygB/6YB/6QB/wUB/x4A/9gA/1EA/wIA/9UA/xkA/xYW/xgW/xAW/zoW/zUW/wIW/xoW/zcW/vLx/vPx/uzx/vbx/u3x7/fuEqASQPZ3DwAAAAFiS0dEBfhv6ccAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAD3SURBVDjLY2AYOMBIJGBgIhIwMBMJGFiIBAysRAIGNkzAzoFFkIETDXBxcXJzcXKhC3My8KABXh4+fgEeTMAgiAyEhIVFRMXEJURFhIUlUWQEMUwU4JGSluGRxTQRxVY5eQVFJWVpFVVVVTV1IB+nQg1NaS0o1ObDrRCkVEdXWg8I9Q1QlWEoNOQxMtYzkTY145HFqxDoFXNpE2M9aQse9CBCDR5BYVFLcStrG3FbUTtBfMHDy2Pv4MjjxGPu7ILhRldk4Obu4enl7e3t5enj7oYi48rgiwL8/P0DgFSAf6AfqoQv8cksiEjAEEwkID5zEZ1dBw4AAEpqU4Ui4NrQAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjU3KzAyOjAwFHE73wAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo1NyswMjowMGUsg2MAAAAASUVORK5CYII=", "name": {"common": "Suriname", "deu": "Suriname", "fra": "Surinam", "hrv": "Surinam", "ita": "Suriname", "jpn": "スリナム", "nld": "Suriname", "por": "Suriname", "rus": "Суринам", "spa": "Surinam", "svk": "Surinam", "fin": "Suriname", "zho": "苏里南", "isr": "סורינם"}}, "SJ": {"currency": "NOK", "callingCode": "4779", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAASBQTFRF1ygo2Csr1Rwc4WVm////2N/tATmYADeXBDuZACqQmazS43h61BkZ2Cws2C4u1R8f4Who43p81R0d2C8v1BgY1Rsb0gsL31pbmKzS4W9w0QkJ5oqM5oyN5YSF6qqs1d7tACuQl6zT6rO25YKE/f3+2eHwAjmYmbDW+/z9YYC7YIG8ZYfAU3m5ATiXADiXADOUOmavaInCX4G8AC2RADmXADCTACqPBj2aBz2aBTyZAC+SADGTMV6rMV6qMF2rMl+sKVioATmXADWWHU+jNGGsMF2q2eHvmbDV///+2OHvma/V3lFR31RU3ERE7KWm2N7smK3T772+4mNj5G9v5G1t1iMj0g8P6IyM6Zmb1R4e1yoq1Boa6ZGS6qCi6ZCR6p+gJDeP5AAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAD0SURBVDjL7dLZUsJQDIDhtEYFhSJaFgG1oiKLHtwqiCwqiAvuyuL+/m9hO+F0mDnBJ+C7/iczyQSAaPoUTs/M+vxzOB/QYLxJyIdBYoQWZBheXAqqwCSRaIzCOC4ndFMFSZJaWcU1J7TWMb2RSqoApU3cyjgTtzGLHMhJ+cLOrsgU9/YPchywPP5DIcSRfWyxoOQpnzih8JVLLLA9lVN3YrFis6A6VKs3zoQ4v2i2Lquc0a3b7h2vMHvNGQ1v3PB23Hk65O7+AR/d8AmfX17fVBAi3V6fwjgOou8RFXwQ4/NLht+6oam8JzJ/ZPj77z9OQjb8AwhfWkSfb+ooAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjEyKzAyOjAwLcFxvAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToxMiswMjowMFycyQAAAAAASUVORK5CYII=", "name": {"common": "Svalbard and Jan Mayen", "deu": "Spitzbergen", "fra": "Svalbard et Jan Mayen", "hrv": "Svalbard i Jan Mayen", "ita": "Svalbard e Jan Mayen", "jpn": "スヴァールバル諸島およびヤンマイエン島", "nld": "Svalbard en Jan Mayen", "por": "Ilhas Svalbard e Jan Mayen", "rus": "Шпицберген и Ян-Майен", "spa": "Islas Svalbard y Jan Mayen", "svk": "Svalbard a Jan Mayen", "fin": "Huippuvuoret", "zho": "斯瓦尔巴特", "isr": "סוולבארד ויאן מאיין"}}, "SZ": {"currency": "SZL", "callingCode": "268", "flag": "data:image/png;base64,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", "name": {"common": "Swaziland", "deu": "Swasiland", "fra": "Swaziland", "hrv": "Svazi", "ita": "Swaziland", "jpn": "スワジランド", "nld": "Swaziland", "por": "Suazilândia", "rus": "Свазиленд", "spa": "Suazilandia", "svk": "Svazijsko", "fin": "Swazimaa", "zho": "斯威士兰", "isr": "סווזילנד"}}, "SE": {"currency": "SEK", "callingCode": "46", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAJxQTFRFAEBzAD90I1NjG05nAD50AD9zHVBmG09nPGFYM1tcGk5nHE5m37sO37oO5L0M4bsN3bkP3roP5L0M/80A/8wA3LkQAEBzAD90I1Nj3LgQGk5nAD50AD9zAD51I1JkGk1oAD11578L4LsO/MoC+skC4rwN4bwN5r4L/MoB+8oC470MIFFkHlBlP2JXNl1bHU9mH1BlI1FkI1Jj////hM0NagAAABJ0Uk5T/Pz9/Pz8/v7+/v7+/v7+/v7+yMbBHgAAAAFiS0dEMzfVfF4AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACNSURBVDjL7dHJDoJAEEXRh+KE4tQlqC2COA/g8P8fp0AKNxXSC+OKs75JJa8AZjWmikjNmnZLAo/58zxcLPVKUoc/Cn0WrIswjLQEbdbpbrIw7jl9CQbMHW7pYzcaTyRQX5RTMpAh89D49J4djqesO19iEa7slhTzpPeHBOWi0bN68PJH+lX9wjr8b/gGzuNz038exeMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NTYrMDI6MDCyBjBrAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjU2KzAyOjAww1uI1wAAAABJRU5ErkJggg==", "name": {"common": "Sweden", "deu": "Schweden", "fra": "Suède", "hrv": "Švedska", "ita": "Svezia", "jpn": "スウェーデン", "nld": "Zweden", "por": "Suécia", "rus": "Швеция", "spa": "Suecia", "svk": "šveédsko", "fin": "Ruotsi", "zho": "瑞典", "isr": "שוודיה"}}, "CH": {"currency": "CHE", "callingCode": "41", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAFFQTFRF+gAL+gMO+gQP+gAC+gAA+gAJ+0xU/GRr/F9m+gEM+gAG/svN//////z9/sjK//n5+gIN+gAH+gAF+gEH/svM/tja/srM//T1//7//GBn//v7oSjeMQAAAAFiS0dEDIGzUWMAAAAJcEhZcwAAAEgAAABIAEbJaz4AAACkSURBVDjLzZPdDoMgDIUFK1Io4L/o+z/oILswy9yo2bLsXLXJF0hPe6rqjyRkkmCANSTVZa5RrdatakocGkvOkTVYBD2FQJ4B8l7EA8R3FGLX38G+y92LeQdjzAhTBicYUzOczS5Azd5au6wUI61LKv2s4Nl5CRvt9KCdNpAnoKbgkmJmYq4C6c9A5tfsYdj2XDCcv8IrR/H9M+NHgR8uflx/phvpaw7xytebpwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDozOCswMjowMCRWQrEAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MzgrMDI6MDBVC/oNAAAAAElFTkSuQmCC", "name": {"common": "Switzerland", "deu": "Schweiz", "fra": "Suisse", "hrv": "Švicarska", "ita": "Svizzera", "jpn": "スイス", "nld": "Zwitserland", "por": "Suíça", "rus": "Швейцария", "spa": "Suiza", "svk": "Švajčiarsko", "fin": "Sveitsi", "zho": "瑞士", "isr": "שווייץ"}}, "SY": {"currency": "SYP", "callingCode": "963", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAMxQTFRF/wAA/wEB/xkZ/xgY/xkY//T0/vTz/vT0/////v///f/+//7+/f79tuG13/Lf/v7+zerMxefF7fjtRLJEn9ef7vjudsd2Zb9l6/frmNaYHqUeCZsJDp4OOrA61e7VvuW+LKssDJ0MC5wLJqgmsuCy/P78f8p/AIsACJcI0u3Ss+CzAJEAAI4AotmiX79fc8dzSrZKrN6s+/77jtKOV7xXZMFkesp69vv2zuvO8Pnw+Pz4/v/+/f39//7/9PT08vPy8/TzGRkZAAAAAQEBn8Z0IQAAAAFiS0dECIbelXoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADOSURBVDjL7cxpU8IwGATgV1rKpgSKIMqhXF6oeHAXq63C//9P1DZJD2acfIdnMpNkZ2eJTv5zpkl/saAYZiHPNNSTiopVKuZZlnoSBMZsxpDF7CSjssBRqcIpp3HUzsHFRy46dTQu0MwM1nF5hZZcFD20O93rmx64qnH0B8PR7Z2I5OI9Hh7HT6neX/T8MnkNr1zx7f1jKtPIDPPFchVecdGJYe1iEy46KXA/PffrO/5QsuDhgJdk5EuB/xP4WUF0YvSribaaaKdJf/Eoi3sV+oSvKztrfwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDozMCswMjowMBe5DNYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MzArMDI6MDBm5LRqAAAAAElFTkSuQmCC", "name": {"common": "Syria", "deu": "Syrien", "fra": "Syrie", "hrv": "Sirija", "ita": "Siria", "jpn": "シリア・アラブ共和国", "nld": "Syrië", "por": "Síria", "rus": "Сирия", "spa": "Siria", "svk": "Sýria", "fin": "Syyria", "zho": "叙利亚", "isr": "סוריה"}}, "ST": {"currency": "STD", "callingCode": "239", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAdFQTFRFgUwAAaMAAp0AAZwAAJ0A+gEAi0YAAKcAA50A6AwA/wAAjUQAAKYA6woA7ggAj0MAAKUA8gUA7QkAkUIA6wkA8QYAk0EA7AkAlEAAAKQABZ0ABJ8AlT8AAJYAAJQA7goAokMAZc8Ab8gAbcUAbccA8QsA8QUA7gkA+wAA/2oA//8A//4A8QoA7AAA8mAA/P8A+/wA+fsA+v0A+/4A+/0A+vwA8AAA9mEA/v4A8goA7QAA+2gAw8QA6ekA7e0AwMAA/PwA7wYA9ggA/wUASQcAuMUAw8MAREQA/f0A8AYA8gYA7gYAdwMARAUADAAAMDIAU1IAxcUAzMwAVVUAMzMACwsAR0cAeHgA+PgA8QkA9AYA2wYAPQUAAAAABAEAnZ0ApqYABwcAOTkA4+MA7QcA/wgAWQAAITEAGBQAz88A2NgAGhoAIiIAUVEA8AcA9QcA4AAAhnQAu7kAra0As7MAiIgA5+cA8QcA8QAA954A/f8A/vwA+/sA8QgA7QYA9ZoA+fkA/P4A/gAA/6AA//0A8AgA+AAAhWwAadgAbsMA7wcAZ14AApIAaVwAAKkABZsABJ4Aa1oABJsAblkAcFcAAKgAclYAdVQAd1MAA5wA////YmTFYgAAAAFiS0dEmpjfZxIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGJSURBVDjLY2BgZGJmIQIwsLKxcxCjkoGTi5uHGJUMvHxc/AJEqGTgFRTiEiZCJYOIoKgQlxhhlQzigqJAlRKSHFLSeAFYIVCljKSsnCw+wCAPUghUqaCopKyCBzCoqoEUiqpraGppa+EBDDoQhaLqunr6BoZGxiYQYGxiCmNCAFyhqI6ZuZaFBdwEJCaqiaKillbWNrZaEHkLLTt7LQccCh2dnF1cobIOWm7uWh4W2BR6enn7+Pr5BwRqeQBhUHBIaFh4hBZCKUMkzETPqOiY2Lh4sJyFVkJibFIykjokhaIpqWnpGZlA47SAOCs7JxfFkQiFefkFhVpFCWCFDlrFJVqlZVhNLC+vqKyqtqiBitdYWHig+roWqq5Ot76qwcjQxBga3oZGho0oAQ5RmJfX1KzVgjcKW8HqytvaOzpV8CaKLrB5XN3sPbL4k1keUF0XV29f/wQCCTcPpG5i3ySCeSbPs4trMmF1LAxdXVxTphJWB1TINY0YdSwMTdOJsBekcAbPTGLUsQAAE1aYJc9tpZgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDkrMDI6MDCI5EAcAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQ5KzAyOjAw+bn4oAAAAABJRU5ErkJggg==", "name": {"common": "São Tomé and Príncipe", "deu": "São Tomé und Príncipe", "fra": "São Tomé et Príncipe", "hrv": "Sveti Toma i Princip", "ita": "São Tomé e Príncipe", "jpn": "サントメ・プリンシペ", "nld": "Sao Tomé en Principe", "por": "São Tomé e Príncipe", "rus": "Сан-Томе и Принсипи", "spa": "Santo Tomé y Príncipe", "svk": "Svätý Tomáš a Princov ostrov", "fin": "São Téme ja Príncipe", "zho": "圣多美和普林西比", "isr": "סאו טומה ופרינסיפה"}}, "TW": {"currency": "TWD", "callingCode": "886", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAdFQTFRFCDmcCjqdCTqcBzebCzmaADumXi9k7R8G2yES3iEQAzKZBzicDDydGEOhBjWaCzudPmWyAzKYLlWsobPaACeUKlOoLVerBDaaCjudCDqcBzSaAB2OgZrNqrrdOGSpsMjaK1am4OX0JU+nACSSAjSaR2y1Sm23OGOsa564apu4gKnDYJazapa6L1esZIPBJ1KoK1Wq2+LwTIaqutDf/////f3+ZZa3lbjMr7zgATSZDz2gG1OcjbLH+Pr8+vz9+Pr7RX6nJVKnDDyeCTqdAzWaIk+mzNTsk7PJpcHU/P3+aJW12ePsjKPSCzydCTmcET6hGFKbj7TI/v7/+fv8SIGoHk2kKFKp1t/uwNTiapm5jbLJvMflBzmbRWu0TG+4M1+qZ5u2cJ+7hq3GY5e0aJe5Vni7CDqde5TKqbrcLlylq8XYIU+i2+HyLlirCTmbDDucCDibPWOwBzWZKE+oorPaACqVIkukLVaqBTaaCzucDDmZADulXi9jBDmeBTqfAjObBzufFEKjBDqeBDadAzWcADuoXDBlFzeSFjeSGDmTFziTFDWRGDiTGjeQZy5e7B8HzyMa0CMa0CMZziMa1SIW3yEP4iEO4iEN4CEP3SER3SEQW/bs6wAAAAFiS0dENKmx6f0AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEdSURBVDjLY2CAAkYmBmYQAQIsrGzsHJyoAKaOiYubh5eBj4+JgEJ+BgFBIWERUTFxCbwKJaWkZWTl5BUUlZSZJfEoZFJRVVPX0NTS1tHV01dhwq2QUcXA0MjYxNTEzNzCEr/VVtY2JrZ2JvYOjk5MuBU6u7i6uXuYeAKhl7ePL7cfbhOZ/AMCgSYGBYeEuuAxEejGsHCjCKCBkVHRMQx++HwdGxefkJiUnBKWitfXDGlS0ukZmVnZObnKzLiszgOD/ILCouKS0rLyikogr6q6BlNhLQTU1dc2NDY1t9SBOAytbZgK2yGgo7Oro7urp6cDxOnN6+vHUDgBDiZOhLEmTZ4ylRNd4TRsYPoMTgyFM7GCWZgKOYkEowqHvEIA5+WDvzAavScAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NTkrMDI6MDBETkCCAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjU5KzAyOjAwNRP4PgAAAABJRU5ErkJggg==", "name": {"common": "Taiwan", "deu": "Taiwan", "fra": "Taïwan", "hrv": "Tajvan", "ita": "Taiwan", "jpn": "台湾(台湾省/中華民国)", "nld": "Taiwan", "por": "Ilha Formosa", "rus": "Тайвань", "spa": "Taiwán", "svk": "Taiwan", "fin": "Taiwan", "isr": "טייוואן"}}, "TJ": {"currency": "TJS", "callingCode": "992", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAWhQTFRFzRc4zRU2zRU0zRU1zRU39tbc9tba9tbb9tbo9tbl///////+//+7///P///9//39//37//3///2E//3L//2G//2o//2v//2Y//36//+V///V///8//+9//+m///3///d///c///l///6///f///U//9O///4///D//9a//9d///i///e//9L///x///u///o//9Z//9w///5///7///s///k//+E//+r//9V///H//+B//+q//+f//9v///p//+T//+g//+y//9W//9l//+///+P//+u///q//+v//9x//9i//9+//+d//+h//9z//93/f79/f78/f7//f7W/f5o/f5N/f6Q/f46/f5P/f6N/f5B/f6C/f7j/f77//+x//+W///C///X///B///A//+L///K1OzZ1Oza1OzY1Ozk1Ozt1Ozo1Ozl1OzgH508H509H506H504H507H505JJ9BI59AIp8/J6FD5kCDSwAAAAFiS0dECmjQ9FYAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEdSURBVDjL7ZDHV8JAGMQ/G9GJrsauEURUUBRRsCEqKvYu9t6lRCXE9u+bI7t55OXmxd9hDzPzZt5+RP/YUWalvKLSKlKVgEuSqmskySXqBBEZtXVgFpnqORqUxialuUVpbVPaecfS2NEJpqLLLZbyQU+3bE57e8zH18tKBhn6+v0qAgODQWBoOGTGSzX6RmQZ4dGxCBAdZzbTEx4wPyanEGEIhW2C0zGwKGbiUFXMznH/ERrnkVhYXEour6wiuFa6Ucb6xuZWfHvHvZvci/EHov1iDlKHR8cnp2fnF5dXqWvO4qcZbm7vAt77h8enZ+Hi9FJMOpPNaVr2VdNyb5k0Z9E7R14v6Lph6B8FI8879OkQ+nLIXwa/HUI/DvkFp7PQDyhhj5YAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MzMrMDI6MDAmURZLAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjMzKzAyOjAwVwyu9wAAAABJRU5ErkJggg==", "name": {"common": "Tajikistan", "deu": "Tadschikistan", "fra": "Tadjikistan", "hrv": "Tađikistan", "ita": "Tagikistan", "jpn": "タジキスタン", "nld": "Tadzjikistan", "por": "Tajiquistão", "rus": "Таджикистан", "spa": "Tayikistán", "svk": "Tadžikistan", "fin": "Tadžikistan", "zho": "塔吉克斯坦", "isr": "טג׳יקיסטן"}}, "TZ": {"currency": "TZS", "callingCode": "255", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAPxQTFRFAJkAA5oABJkAAJEALqoA0OoA//8A9fYAZ2cAAAAAAQEAAZkABJsAAJUAApcAc8YA9PkAxMQAKioAApoAA5kAKqkAxeYA9PYAcHEAAgIAApYAdccA9vkAwMIAJicAAwMAxOUA9PUABAQAKCgBc3UD9fYCJygBwcMD8vgKcXID9PYDwOQ7LarRwcME9fkHdceHApb9AJT/xOU4KqnUAJH/A5n8A5r8dMaIAJX/BJv7AZn+AJn/Apr9cHIDAJQAdscALaoAweQA8/kAcnQA9vkHJycAxOU3dceIcXIC9PYCxeY2KysAxcUB8/kJcsaKaGgA0OorLqrQBJn7////k/P2mwAAAAFiS0dEU3pnHQYAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADxSURBVDjLjcx5WwFRGIZxjN56TrK00BDTKEubKGsnVEqFtHz/D9OcsV0xJ+/99339fD59/oAR3KDNLQgA+m07tBOORCm26376cW/fOIhT4lDAxD+jwyVTR5TOwJp80HPHZDtcVkw+k8cJCzzOwgmHywKnORaXL1CRwYmzc7q4ZHBXVLouVzjczW21VmdwjXKzdSdZ3L2UksfNRm+uveCm43rOHTmcGv0cTo2dZHc9p8aHRwanxicOp0a7hyXueZVT48sy1/fg1AjrL/fqxamRxbkih5uPJsTbOw203GwUGI7oY6znpqPAZ46+vqs/danvF4pIe3odrT25AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjM3KzAyOjAw0h4yWAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozNyswMjowMKNDiuQAAAAASUVORK5CYII=", "name": {"common": "Tanzania", "deu": "Tansania", "fra": "Tanzanie", "hrv": "Tanzanija", "ita": "Tanzania", "jpn": "タンザニア", "nld": "Tanzania", "por": "Tanzânia", "rus": "Танзания", "spa": "Tanzania", "svk": "Tanzánia", "fin": "Tansania", "zho": "坦桑尼亚", "isr": "טנזניה"}}, "TH": {"currency": "THB", "callingCode": "66", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACpQTFRF5wAA+9HR/////vz9LkSsABKWAh2bABuaABiZ0dbt/fz9/e7u6RER5wEBg9UTbwAAAAFiS0dEAmYLfGQAAAAJcEhZcwAAAEgAAABIAEbJaz4AAABISURBVCjPY2CgFxDEAhiUsAAGYywAu0qsgi5YAEMoFsCQhgUwlGMB9BPswAIYZmIB2P2+CgvArnI3FsBwBgvAHpt3sQDi0wIAVyKx+Q1baa0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDYrMDI6MDB+rDD1AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQ2KzAyOjAwD/GISQAAAABJRU5ErkJggg==", "name": {"common": "Thailand", "deu": "Thailand", "fra": "Thaïlande", "hrv": "Tajland", "ita": "Tailandia", "jpn": "タイ", "nld": "Thailand", "por": "Tailândia", "rus": "Таиланд", "spa": "Tailandia", "svk": "Thajsko", "fin": "Thaimaa", "zho": "泰国", "isr": "תאילנד"}}, "TL": {"currency": "USD", "callingCode": "670", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAmRQTFRFShsF0DQO1wgQwwAPywIPzAMPywAPAAAAKSMCwW8L6kMPxgAPxwAPzAIPAgEACAwAlHwH/qEN2zkOyQAPAgIAAwIAWk0E88cM950N0zIOyAAPAQEABAMAJhwCzacK/9gN6pEN1jUOygAPBwQAlnEH/9YN+ssM65EN2DUOXEQE8rwM/9IM9sYM7ZMN1zQOKiACz58K/8wN98cM+cgM7ZIN1zMOAQEBCQcAnHgI/8sN+L8M+skM7ZAN1zIOAgICBAMBAwMBYksF9cEM/cUN87oM+soM7Y8N1zAOEBAQDQ0NAwMDBAQELyQC1KUK/84N9LoM9r0M+8oM+sgM7I4N1i8OOjo6paWlGRkZFhYWBQQBDAkBon0I98AM9bsM+L4M7I0N1i4OIyMj////kZGRERERjIyMzc3NaVAF+MMM/ccM9LwM7IwN1i0OGhoa8/Pz/v7+8fHxampqNCgC2qoL/88N974M+MAM+8sM+scM64oN1SsOyAEPU1NTv7+/+/v71NTUBgYGCwgBnnsI/8YM9r8M970M+cQMywEPcnJy5+fn/f39V1dXkHAH/8cM9r4M8aEM2TsOFBQUurq6ysrK3t7e/Pz8RkZGKR8CzqEK7pUN2DcOl5eX8vLyDAwMMjIyR0dHXEcE8r4M/8kN7pQNiYmJCAYAmHUH/80N+MIM9LsM2DYOLi4uBAMCKSACzaAK7pMN/sYNCAcAzZ4KXEUE/9MM9sUMCAUAmHQH/9cN+csM6o4NKh8C0KoK1jIOX1IE9ccM9pkN0y4OCg8AmoAH/p0O2TQOLSYCxW8L6T4PTxwF0jIO1gcQblB1CQAAAAFiS0dEYiu5HTwAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAIISURBVDjLY2BgZGJmYWUjCBjYOTi5uHl4eQkq5GPnFxAU4hYmZCoDu4gou5i4hKSwFAsvfoXs0jLssnLyCopKeJUCFbKzy7Arq6iqqWvgMxWsEGS9ppa2jq4ebqUQhSDr9Q0MjYxNTIWFsSsFKzQzA1lvbmFpZWRtY4tdKcREO6BKewd2RydnF1drN3dsSoEKzdg9PNm9vEHW+/j6+QcEBgWHYCoFKrRjDw0Dqg6PYI9kj4r2i4mNcw2KTxAWRo0tsMLEpOSU1LQUdjNREfb0jMysuDi1oOwcoFIWNKtz8/ILkgrZvcG+LyouKS0rjSuvqKySqkZR6FlTm1SXVN9gxg4J/Mam5paystY46zZdW6V2hEKzjs6k/K78pG52L4hC6Z7evrKy0tIK6/4JyArZ2b0mTkqaPGXqNKBrgVZPnwGyutTadeYsYSlUN3qzz54zl33efHZ2UVH2BQsXZcWVurounoWaSEAmerMv6QD5BGjt0mXLV6yMAypbBYx1VrTgAXp7tQe7md0aGfa16/xWBgS6rsdQBk8UZuAUtGDhBhdXV10NTGUwhcBUIcO+cZllmVGgCVZlyMls7SZQMtPDrgwp4W7W2rLVhFDClWHftn3Hzl2mBLIC0Nrde3bs2itMIHMBrd23/8BBXFkArpCP/dDhI0e5CSgDmXjs+AmiipSTp04TVUgBALXfkEz9cz3PAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjI0KzAyOjAwwJ5DZQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToyNCswMjowMLHD+9kAAAAASUVORK5CYII=", "name": {"common": "Timor-Leste", "deu": "Timor-Leste", "fra": "Timor oriental", "hrv": "Istočni Timor", "ita": "Timor Est", "jpn": "東ティモール", "nld": "Oost-Timor", "por": "Timor-Leste", "rus": "Восточный Тимор", "spa": "Timor Oriental", "svk": "Východný Timor", "fin": "Itä-Timor", "zho": "东帝汶", "isr": "טימור לסטה"}}, "TG": {"currency": "XOF", "callingCode": "228", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAZtQTFRF1wIA1gIA3AAAxQ4AHIEAEYkAFIYAE4cA2AAA1wEA3QAAxQ0AGYAADocAEYUAEYYA2AEBGoAAD4gAEoYAG4AAE4YA1wAA2xUV2hMTxQwAF38ADIYAD4QADoUA2QQE2QgI1QAA6nd352dn1gAA2QMDxg0AJIQAGowAHYoAHIoA1QEB1AAA+M/P87S00wAA1QMD2AEA2hUA6NEA6d0A6doA2g0N6nNz63l57ICA/vv7/e/v7IGB7YeH6XBw2AIB3BYA/dsA/+gA/+QA/+UA5VVV/fHx//////z8++Xl4kFB3BUA/NgA/uUA/uIA2xgY+t/f+NHR2AsL/dkA/+YA/+IA/+MA2AIC2x4e/vb2/vX1/vn5/O3t2hUV/uMA3igo3zAw5VNT2xkZ52Fh2wAAyg4ASpMAQpwARJkAQ5oA1gEB1wEB1wICxAwAEHwABIMAB4EABoIAxg4AG4EAEIgA1gEAwwwAEn0AB4UACoIACYMA9q4A9a4A960A8bEAyswAx84AyM0A/+4A/+cA/+EA/+AA/uEA/OIA69sAHosA5rKH+QAAAAFiS0dERhe6+e0AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFHSURBVDjLrdLXUgIxFIDhI7aIhQiyYgEWF+wFe0VFdMHe14LYsPfee39sQ0Bn1qxOLvyu/znJnARALU4Xn5CYlMwCpJaiT01LzzCwvkOMv8JMo4kvzDKaTaxYiIVsi4BpmJObl29lRUObXXQUSHYnCXWuwqLiElY0FEvLyisqq0SE3Ki6prZOAwkxqm9obGpuaW3z2FG70NHp7fKxgF6wu0f2++VAr4DdQl//wKAGejQWh4ZleWRUxIiEY+MTkywaKsLU9MxscA7RkEwMsWhoEx2B+YWgR7SR8I87EosWSVpajuwxvLK6tr7BioWbWHFubePIwnd29/YPWLFQIQ1W6Mu4uH/PL5/CrQb6w6Njs9ZEQe0kfHp2fnHJgiu165vbu/sHDfDI8D55NcDzDy+vb5ogxAl8nOCdE3xwAisnMHECA6f/Dz8Bhgr8J1Oa6VUAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NTErMDI6MDB3oQ7lAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjUxKzAyOjAwBvy2WQAAAABJRU5ErkJggg==", "name": {"common": "Togo", "deu": "Togo", "fra": "Togo", "hrv": "Togo", "ita": "Togo", "jpn": "トーゴ", "nld": "Togo", "por": "Togo", "rus": "Того", "spa": "Togo", "svk": "Togo", "fin": "Togo", "zho": "多哥", "isr": "טוגו"}}, "TK": {"currency": "NZD", "callingCode": "690", "flag": "data:image/png;base64,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", "name": {"common": "Tokelau", "deu": "Tokelau", "fra": "Tokelau", "hrv": "Tokelau", "ita": "Isole Tokelau", "jpn": "トケラウ", "nld": "Tokelau", "por": "Tokelau", "rus": "Токелау", "spa": "Islas Tokelau", "svk": "Tokelau", "fin": "Tokelau", "zho": "托克劳", "isr": "טוקלאו"}}, "TO": {"currency": "TOP", "callingCode": "676", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAANJQTFRF/////vv76J6evQAAwgQEwQAA/PHx+OTk+urq//7+/vr63XR0zCsr/fn53G9vugAAySMj/vn5/PPz22xsuwAAyiMj/PLy/ff3/vz82mdnxhMTyiYmxA4OwAAAwgUFySEhyB4e+uvr0FFRtwAAuwMDvgAAwgAAuwEBvgwM+ejo//396J+f2mho33x8zTIyxRAQ3HBw3XFx3nl5vAAAyicn2W1txiMj/fX1/v39/fj45pqa0D8/55yc4YWF4YOD4IGB4YKC446O1VJSvwAAwQICwgMDuoAxsAAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAACySURBVDjL7dHHDoJAEIDhWQF1VxSxsKBiQ2zYEHuv7/9KGmIMJrJLPPtf5vJlkskABEMQE0Tpay8RTyQxH2ICKUFOA+ZuRJBRsiofkhzkC0VNpZgJdTBK5YpZrdXlBrBh02rZbbOjdHtM2AdnMBzZ44k71TD3GNebRThGpzC3FssVYUL0bA2brUF1gijsOJ95v9AJhXu/w/Hkj/PleguBXjDBs6WQQPzsHgqliP3hH/4OH2kvI7hBH2EDAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjM5KzAyOjAwgiFJBQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozOSswMjowMPN88bkAAAAASUVORK5CYII=", "name": {"common": "Tonga", "deu": "Tonga", "fra": "Tonga", "hrv": "Tonga", "ita": "Tonga", "jpn": "トンガ", "nld": "Tonga", "por": "Tonga", "rus": "Тонга", "spa": "Tonga", "svk": "Tonga", "fin": "Tonga", "zho": "汤加", "isr": "טונגה"}}, "TT": {"currency": "TTD", "callingCode": "1868", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAjRQTFRF8pKS/v//SkdHAAAABwcHAgICBQUFXFpa////7oeH3QAA4QUF4AEB4AIC4AMD3gAA9ZiY9P//Ozg4BAQEbGtr7HV14QQE4AAA9qGh8/7+NzQ0bm1t63Fx3AAA96en7/r6Mi4uBAUFdHJy6mlp+K6u6/b2LCkpeXd36WJi+ra25/LyJyQkBAMDfnx86Ftb3AIC+7294+3tIx8fAwMDAQEBg4KC51RU3QUF/cTE3+joHhsbiIiI5k5O3QgI/srK2uPjGhYWjY2N5UdH3wAA3QsL/9HR1d3dFhISAwQEkpOT5EFB3g8P/9fXz9fXEg8Pl5mZ4zs73hIS/93dytHRDwsLnJ6e4jU13hYW/+PjxMvLCwgIoaSk//v74S8v3xsb/+jovsTECAUFpqqq//j44Soq4QMD3x8f/+7uuL6+BgMDAQAAq6+v//T04CUl4CQk//Lysre3AwAAAQICAgEBAgAAsLW1/+/v3yAg4Skp//f3rLCwBAEBtLu7/+rqpaioAgMDucDA/+Xl3hcX//7+n6GhvcXF/+DgmJqaCgcHwsvL/9ra3Q8PDAkJxtDQ/9TUi4uLy9XV/87O3AgIhISEAwICEg4Oz9ra/8jI3AUFfX19FBER097e/sHBd3V1FxQU1+Pj/bu7cG5uGhcX2ufn/LS02wAA6mpqaWdnHhoa3uvr+62t7HFxYmFhIR4e4u/v+qen7XZ2X15eIyAg4vDw+J2d74iITkxMCAgICQkJLy0t8fj49ZWVDCFcogAAAAFiS0dECIbelXoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAICSURBVDjLddT5Ww1hFAfw4ZjrmDvvXAzz2kaFCCnckS2iCRVRJFtFuOjay74nRPZ932Xf9+Wfc17vczXde9/vz5/nPOc5z3O+mtatO/TQ9RD0xF5G2AyGJWJRtEjvPn3BBtChH/Y3HK6KZhkDBg4S0obBOMQIKyGz3KEZmUJmwTAcrpQaN5k7InuknDkKc1RS40KOHjMWcoUch3kKSZAzM3/8hIkQBc+GSViQXgrIGZ88Zeo0KadjYVr5D5KcMbNolpSzsTidlJAzv2TO3HlClkIZlkccFSQ5f0HFQjq7F4VFWJkqE5BbftXiJdVSLsWaFPkfkly2fMVKKVfV1iXLTkiyfvWaBiF1WLtufZIMQB7zN2zc1OgJGd+8ZWtXGYQ8Ftm2fcfOpmZobtq1e89eP6aC3Ins238grnugewcPHa4Pyq6QZF3tETgqZOOxluO+pYIka1pPQJQkNJw8VdUpkyHJSmyT8vSZ9rM+U0GS5VgGpUKeO3+hJCFTIXeMYrwItpDVly5f4UwFedgoxKtgx+EaXL9x85aU6SDJArwtniMKd+7eyzeZCpLMw/v0bSQfPHzkMlMFSebgYwgJ+eTpsw6SCkjyOb6Q8uWr1x0WU0GSb/Ct2DMX3r3/4FpKyB33I36iE9HMz1++Ghr1T6KLAv1E0HTcb/gdfoRCP+HX7z9/Ac3knLPtY9BvAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjA2KzAyOjAwFSRVMQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowNiswMjowMGR57Y0AAAAASUVORK5CYII=", "name": {"common": "Trinidad and Tobago", "deu": "Trinidad und Tobago", "fra": "Trinité-et-Tobago", "hrv": "Trinidad i Tobago", "ita": "Trinidad e Tobago", "jpn": "トリニダード・トバゴ", "nld": "Trinidad en Tobago", "por": "Trinidade e Tobago", "rus": "Тринидад и Тобаго", "spa": "Trinidad y Tobago", "svk": "Trinidad a Tobago", "fin": "Trinidad ja Tobago", "zho": "特立尼达和多巴哥", "isr": "טרינידד וטובגו"}}, "TN": {"currency": "TND", "callingCode": "216", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAa1QTFRF5wAT5wIV5wQX5wMW5gAK5QAC5gAJ5wIW5gEM5gAH6yk68Wx39I6W9I+Y8nB77C8/5gEK5wMX6iEu+LG3//z8////+bvB6yo35gAF7DtG/e7w//7+/ezt/Nzf/vHy/vb37klU5gAM6iEt/ers96yz7DRD6AcZ7kVT//r6/vT26y865gAG96es8neD5QAA5wwT96qy//v8//v7+Lm95gAL5wEV6iUz/vX1962z5QAD5gAN9660+svP9pui7DRB8F9q7UhQ7UBN+s3P6BQf96208W558nN984CJ/vHx6iAu84GK9I2W7DhH5wIT8Wpz/vr69ZWd83+J84SN96et7T9O5wAO5AAA9ZWb/vj49ZSc5wQW5QAE8F5p7UpS7k1a97K16Rok9Zih7ktZ/ODj8nF76iMx/vT05wIS+b3B+LG2/vT17DI/9qSp836I5w8X96mx//39+La66R4q/efp+LO57DpI6Akb7UJR+LK4/vn6/vHz6ys27DZB/ert/vDx/OLl/vP0/vL07URP6R0p96mw+LS66iUy5gEN6iM18GVw9IeQ9ImR8Wl05gEL5wMVuLb+ywAAAAFiS0dEFeXY+aMAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFCSURBVDjL3ZNXW8JAEEUTEojBiuEqGgWxKxoVQQ2i2BUbVuy9d+y99/qbXd7ZwKuefT3fzO7MXYb517A6Ahtd43g9wcBGUYU40RifkJiUbEgRtDyTmCqZESYt3cJp1BMzMiEjK9sqw5ZDN1l7bh6QX1BYJKEYJaU87Z6CowwoVyoqYTZXOVHtctMK1tSiTvXUw9tAVBmNhsgldaoPaFKagZbWtnYv0KHXRRb1nejyK93o6e1T+hHAgEoTB2H1K0MYHhkNjmEcExRxcmoamAnOAnPzC4tLwDKlNWtfWYWkrq1jQ4JtE9iiPIaMZxvYUXb3nCFysM9TxsO4PQeHwNHxyekZznFx6aIGQxCvrskKb27vZNw/ODSWzYmPT4FwJkLPLw4To4Hwanl7//j8MorfmjELB9elkuDyPzFkPLav8Kf5BfpyNUN31ky0AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjAzKzAyOjAwRxx6lgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNTowMyswMjowMDZBwioAAAAASUVORK5CYII=", "name": {"common": "Tunisia", "deu": "Tunesien", "fra": "Tunisie", "hrv": "Tunis", "ita": "Tunisia", "jpn": "チュニジア", "nld": "Tunesië", "por": "Tunísia", "rus": "Тунис", "spa": "Túnez", "svk": "Tunisko", "fin": "Tunisia", "zho": "突尼斯", "isr": "טוניסיה"}}, "TR": {"currency": "TRY", "callingCode": "90", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAActQTFRF8xkw8xox8xwz8xcv8xQs8xUs8x0z8xsy8gwk8gsk8x409Co/9CU68xIq8gQe8g8n9lpr+7O7/eDj//n6//7+/ufp+qiw9UNV8goj8xgv8xMq8yM5+7nA//////3+/eHk+qSu+Y+a+qGr/L3E+HyJ8xYt8yE3/dLX/vLz+HGA8xYu8gch8gYf8gMd8g4n9lFj9lRl8xcu8xwy8g0l+6my//v7//b39ktd8xkx8xw08gsl8x418xQr8xsx9UdZ//r7+HqH8x828xgw8xoy8xIp9T5S9llq8gkh+ZOe/ujq8yA38xYv8gwl9ThM/uXo9CtA9CM5911t/MDH+7jA8xsz8x00915u/dvf/vT1+YyY8xAo8xMr/M3S+qew8gkj8xMs9TtP/LzD+7a98gEa/L7F+7vC9259/vP1/vHz9UNW8xEp+Y6a/uzu9CM68gki/d/i9CxB92R0+HF/9UFU//39+YOQ8gId9UZZ8goi8xEo+qCp//r69ldp8gIc8gok9CI58x818xUt/MfN//f4+IGO8gUf91xt9lNk+6qz/u3v+7a++qCq+7G5/MPJ+HOB9kxe+qSt/dTY/u7v/drd+pei9TZK8g4m8yE48gUemlBhbQAAAAFiS0dEHesDcZEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFvSURBVDjLY2AYzoCRkZEYZUxMzCysDGzshExj5+Dk4ubh5eNnwquOnZVfQFBIWERUTFxCEo8D2KWkZWTlZOUVFJWUVSRxq2RUVVOXldPQ1NLW0dXTNzAEimBXaGRsImtqZq5rwcRkyWhlbS0paWPLiMVX7Px2svayDrqOTiDgzOzk4urGKckhiaGQzd1DVtbTy9vCCQKcfXz9/AMCAwwxTQySlQ32CYGqcwoJDZMNj4iMwrCcMTpGVjY2DqrQIj4hUVZYNimZHYsbU2RlUxEmOlmkyaZnZGaxY7oxW1Y2J1cL5saQPPP8gsIiKYwwYo8qLjGVLS1zBKt01mLIKhev8KnEEj5MnFWy1fY1tcwhTExOdfUNDI02Rka22GLGkL1JtqS5xTqvla+xrV2VAWd6Y2cx6pAVke3s6u7p7WOVZMAN2A05+idMnBQxecrUCga8gNFoWp7hdCbjGbz40yPIUDYGLUNbNqJyA3F5ZlACAKV7S8gA90V0AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjMxKzAyOjAwsc4HYgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozMSswMjowMMCTv94AAAAASUVORK5CYII=", "name": {"common": "Turkey", "deu": "Türkei", "fra": "Turquie", "hrv": "Turska", "ita": "Turchia", "jpn": "トルコ", "nld": "Turkije", "por": "Turquia", "rus": "Турция", "spa": "Turquía", "svk": "Turecko", "fin": "Turkki", "zho": "土耳其", "isr": "טורקיה"}}, "TM": {"currency": "TMT", "callingCode": "993", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAASAAAAEgARslrPgAABKNJREFUSMe1lktoXFUYx//fuffO3HncyTySdB7JJC1N0mdIbTRtpbYNtKGKi6LdFLGVbqSga0FdFQTpwo0UESkI3YgLFUSK1bZEqFAhBftw0klqk2aSySSZTOZ578y953ORuBGK0Ml8cHYHfud3zv8759AnI9gxh6cZCED3eF0Nq2Q7EtQe5upWvBuFrQIMAETEzNikUutlyme4c/il3kMvWoqVmv9hj1nJTpprKbYMUGKDCgmWTBqxZCJiYjS3BlHMsMswdrwxOp765eaNnybygc8amn80Fknoa/NYNwXD7/UnY0k2WVM1N7khmzUWQgM3amvTX//ly012KslVuSuvR7vOSCNJAJEAM+ogQcwwgoaqqKP7Rt3C3axxIA6nbs/8nD1XqZ2MlQaHh94cPfv017uFyalQL6QjFUVNxBKChKaqvVt6a7VaIpI4/cppVKAI5fnP2OVBpBczj/Fw1tTJyirX5p3vTeZYnCrB9Z0WhsdfqBaqVvXx3GOYqFQqpw6fuvrjVQI9v7GUaJShg6tCNJKkb60VVeGBcErs2CDAdhqp9GTAE2jztkEAFRwbOiZIgNEEF6qZx2KOZJRnhgLdQdnmmPf2Bg9N5BZmFXXBQTfArPiV5bXlhewCNFx458L5sfNHPzgKF5rpLtUpIKeLmcP+IwsFMQeh4QjlUgd9FcsZyDtMAMixnf0D+0cHR0f6RxKRxNjHY+MT48InHOk8N5gu7kYgLarbxHKfHtcQa5i39MDOdKGcEu1R+/1BNAAwwAgaQRK0mlsFQD5i2VSsVdVAb0wupaQzUw/thMst46mqb1UmOmS5HYIBAhFAVCgWAAivIKJmXDfAzHBs1ADRqN+YoYYqBmqWCdg2wOB/E8RgKCAiybLJDt4AW2vIz2M6hFkXkqEwwUxxJRjAYA4yB06sUzdqE+9qUc9jRaCym7arOJ6IntjbP+iQswtZA/WVTXF7hrE7jNAyXI/4bwF6MqWvdEwp7EqjowzqaqZR/w/sakN7DAMZBHRM645TmO8yqSfHW8IodwISECBsvrpKBFLhAfpNJOdtSfBZsAEo62FumbFZQukpHvUg60YlrpIg91wjoGB/CjUfEANaoAtAWCso6bg8guIOV7odD8Lc6Hd9sQ+zcdg5sAK0RlvVIzCWcO4uFdz2tqhGQqqZ+tsqurOwkiAHoJYYq24DgW56IY0qZCllAWTY5AUbnShFNia1JFxg2CYXdCV55tTLY6+aDy7NTaTn//C5amvrV3SL8iVqiyiIxJFvPz/54XtmPrOyVD/wWs/Ri/3VAIpzRArQonCVl6AGrUj9TunOlaufXvrqy2nTe7Jr++uRAyPFRQaJFhmrnhCFfMv3f/t9cEg7f9av7fyoY/Dgoz9vi0oxFIdtAnpLlFVWuG+Ybl5JZ6IcGzZ8ypP739zKfHft2FsyH4W0m/7HPgtcXYFk7tnjTN/G7FRR8mVidLRTWw+tXWelE05rHgrBEk/G0XMYXg9IskYKoPSd4OUUl3MQWouEoWoeLDxEeBh9x7F4D0JzPGEEunD3MtSWUQGI9TF9HfUyjDg8YbgDSF+DVYZQsXkP/3/rHyDlCfbydKOTAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjM4KzAyOjAwJFZCsQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDozOCswMjowMFUL+g0AAAAASUVORK5CYII=", "name": {"common": "Turkmenistan", "deu": "Turkmenistan", "fra": "Turkménistan", "hrv": "Turkmenistan", "ita": "Turkmenistan", "jpn": "トルクメニスタン", "nld": "Turkmenistan", "por": "Turquemenistão", "rus": "Туркмения", "spa": "Turkmenistán", "svk": "Turkménsko", "fin": "Turkmenistan", "zho": "土库曼斯坦", "isr": "טורקמניסטן"}}, "TC": {"currency": "USD", "callingCode": "1649", "flag": "data:image/png;base64,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", "name": {"common": "Turks and Caicos Islands", "deu": "Turks-und Caicosinseln", "fra": "Îles Turques-et-Caïques", "hrv": "Otoci Turks i Caicos", "ita": "Isole Turks e Caicos", "jpn": "タークス・カイコス諸島", "nld": "Turks-en Caicoseilanden", "por": "Ilhas Turks e Caicos", "rus": "Теркс и Кайкос", "spa": "Islas Turks y Caicos", "svk": "Turks a Caicos", "fin": "Turks-ja Caicossaaret", "zho": "特克斯和凯科斯群岛", "isr": "איי טורקס וקאיקוס"}}, "TV": {"currency": "AUD", "callingCode": "688", "flag": "data:image/png;base64,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", "name": {"common": "Tuvalu", "deu": "Tuvalu", "fra": "Tuvalu", "hrv": "Tuvalu", "ita": "Tuvalu", "jpn": "ツバル", "nld": "Tuvalu", "por": "Tuvalu", "rus": "Тувалу", "spa": "Tuvalu", "svk": "Tuvalu", "fin": "Tuvalu", "zho": "图瓦卢", "isr": "טובאלו"}}, "UG": {"currency": "UGX", "callingCode": "256", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAASxQTFRFAAAAHxwA4MsA/+kA/+cA/+gA+9IB+9EA+tAA+tME+tIC+s8A4k4H4kwF32Eg341c0pJG4bBy5Jlp32Af3jgI3TYG3F888NHL4NjbalUyvaGC/fn978/K3jkI3jgG20sg79DI////zsfHXFNS29nb4DkI3zYE4YRp/fz+//3/nJybdHVz8e/x//7//fz9wzIHwjEG3qiZ+/n7a2xrSU1Jur2618nG9+7tGwcBGQYAn5eWwL7AR0hHT01Kn0Ms3XVe/vf4n5iXZWRl7uzusbCxS0xNpnlv0lI19eflZWVmGhoax8bH9/f3paOlsa+xp6ep2cO/47ClxsTFNTU3xMPG8vDzs7K1sbCz+ff6xMTHHBkAODUagHxhjIhtn5uBgn5j38oA3cgA4cwC4MsBYexgUAAAAAFiS0dEIcRsDRYAAAAJcEhZcwAAAEgAAABIAEbJaz4AAADaSURBVDjLY2AYAoCRSMDARCRgYCYSMLAQCRhYiQQMbOiAnYOTi5sdQ5iBBw3w8vELCAoJ86KLM4igAlExcQlJKWkZMVE0CQZZFCAnr6CopKyiqKggL4cqw6CKAtTUNTS1tHV0dfXU1VBlGPRRgIGhopGxiamZuaKhAaoMgwUKsLRSVLS2sbWzd3C0RJVBj3onPU1nF1c3dw9PAmnEy9vH188/IDDIi1BqCg4JDQuPiAwmmMyiomNi4+Kjowgns4TEpOTEhMGdzHAAjGSGC6AnM5wALZnhBtRXCAA5O0ad99aTXAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDoyNyswMjowMB60MsYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MjcrMDI6MDBv6Yp6AAAAAElFTkSuQmCC", "name": {"common": "Uganda", "deu": "Uganda", "fra": "Ouganda", "hrv": "Uganda", "ita": "Uganda", "jpn": "ウガンダ", "nld": "Oeganda", "por": "Uganda", "rus": "Уганда", "spa": "Uganda", "svk": "Uganda", "fin": "Uganda", "zho": "乌干达", "isr": "אוגנדה"}}, "UA": {"currency": "UAH", "callingCode": "380", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABtQTFRFJoz/J43+Ior/NZTu8Pcc//8H/v4M//8L////WTbNKwAAAAFiS0dECIbelXoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAoSURBVCjPY2AYwUAQC2BQwgIYjLEABhcsgCEUC2BIwwIYyrGAESIIAFnli3Xgo9d8AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjU2KzAyOjAwsgYwawAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo1NiswMjowMMNbiNcAAAAASUVORK5CYII=", "name": {"common": "Ukraine", "deu": "Ukraine", "fra": "Ukraine", "hrv": "Ukrajina", "ita": "Ucraina", "jpn": "ウクライナ", "nld": "Oekraïne", "por": "Ucrânia", "rus": "Украина", "spa": "Ucrania", "svk": "Ukrajina", "fin": "Ukraina", "zho": "乌克兰", "isr": "אוקראינה"}}, "AE": {"currency": "AED", "callingCode": "971", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAFFQTFRFDqIQEKAQ1M+1zu3QzuzO/wAA4BMAH4cAAJsAAJoA4hMCLY0O+RwZ/x8f/+Dg/////Bwc6szM6Onp6Ojo5QUFQSIiJigoJycn4AAAHwAAAAAA/OpXXAAAAAV0Uk5T/v7+/v48tsTrAAAAAWJLR0QPGLoA2QAAAAlwSFlzAAAASAAAAEgARslrPgAAAElJREFUOMtjYEUBbOwcnNgBw6hCWijk4mZgxA7QFPIwMbNgB2gKefn4cYBRhaQoFBAUEsYO0BSKiIqJYwdoCiUkpXCAUYXUUQgAR6I4y2fbQp8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTArMDI6MDC6XmCVAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjEwKzAyOjAwywPYKQAAAABJRU5ErkJggg==", "name": {"common": "United Arab Emirates", "deu": "Vereinigte Arabische Emirate", "fra": "Émirats arabes unis", "hrv": "Ujedinjeni Arapski Emirati", "ita": "Emirati Arabi Uniti", "jpn": "アラブ首長国連邦", "nld": "Verenigde Arabische Emiraten", "por": "Emirados Árabes Unidos", "rus": "Объединённые Арабские Эмираты", "spa": "Emiratos Árabes Unidos", "svk": "Spojené arabské emiráty", "fin": "Arabiemiraatit", "zho": "阿拉伯联合酋长国", "isr": "איחוד האמירויות הערביות"}}, "GB": {"currency": "GBP", "callingCode": "44", "flag": "data:image/png;base64,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", "name": {"common": "United Kingdom", "deu": "Vereinigtes Königreich", "fra": "Royaume-Uni", "hrv": "Ujedinjeno Kraljevstvo", "ita": "Regno Unito", "jpn": "イギリス", "nld": "Verenigd Koninkrijk", "por": "Reino Unido", "rus": "Великобритания", "spa": "Reino Unido", "svk": "Veľká Británia (Spojené kráľovstvo)", "fin": "Yhdistynyt kuningaskunta", "zho": "英国", "isr": "הממלכה המאוחדת"}}, "US": {"currency": "USD", "callingCode": "1", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAj1QTFRF4bm96r/B6b/B3a6y5bO15bK1wltjyF1j8unr+vDx+vDwyHB2z3J31ZSZ3JicGS9dJTplITdjGjBdKDxnHjNhGzFfKT1oHzZjQjJXtzxFvT1EGjBedYKdXWyNFStaIDVih5OrRFV7FCpZLkJsjpmvdYKeGC5cWmyNQDFWuD5Hvj9GM0dwRlh9NEdwOEtzLEBqTF2BLUFrOUx0M0dvPVB2KT9pTFJ1HDJfeIWgFy1bJjpmhI+oNklxFi1bS12BY3KRFixbUmOF9/f5////aHaUVGSGIjdjPlF3Gi9dfYqkU2SGaHeVUGKFSk5ySlt/PlB3MkZvMUVuKz9qV2eJKz9pO093QDFVtjpCvDpBFSxbgo6nUWKEEilYJjtmj5qwOUxzEihYFCtaa3mXFCxbRDpfRFZ7KDxoJzxnXW2NKT5pYXCQQFJ5UF6BTl+DHjNgcH6aO051HjRhYnGQS1yAUGCD9fT1/vv8/vv7Fy1cfYmjT1+DiZSsZ3aUFS1cRkBkO011M0ZvMERtQVN5QlR6LEBrQ1V7Ok10N0pyL0VvQTBVtTc/uzc+coCcW2qLhJCpLkJri5atc4CcWGmLR0drHzRhITZiaXiWRVd8c4GdIDZiWGiKJTpmPE92YG+PL0NtKj5pJztnIDZjTlh76c/S8dbX8dXXdoOfXm2NiZWsEylZL0JskJuxd4SfFy5cW22OQDNXukZOwUZNwEZNJDllPVB3P1F4MkdwSzxfu0ZOzdLczNHbzdHbzNDb1tPb8NXXWNrergAAAA90Uk5T/v7+/v7+/v7+/v7+/v7+6a2FXwAAAAFiS0dEQ2fQDWIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAITSURBVDjLY+AXEOQXEhbhFxUT5RcRFuIXFODnFxCXkJRCAwzSMrJy8gqKSsoqykqKCvJysqpqaqrqGppaaICBX1tZR1dP38DQyNBAX09XR9nYxMTY1IyBkQkVMPDzm1sYWFpZ29jaWFtZ2lmYq9nbqzk4OjmjAQZpF1dpNwt3D0NPQw93CzdpL29zc28fX2YWVlTAwO/nryzmFRAYFBwUGOAlpuzvFxLiFxoWHoEGGPgjpaOiY2Lj4hPi42JjoqOkE5OS5JJT2NjRAIOQY2paeoZNpn6WfqZNRnpaqmNsrGN2DgcnFypg4M/KzXPLLyjUV9UvLMh3y8stKiwsKi4pLStHBQz8FaKVVYmx1XpKetWxiVWVog41Nba1ddw8aICBv77Bv7HJsLmltaXZsKnRv6Gtvb2to7OrGw0w8Pf0ysn3NSf2T+hPbO6Tl+udqKY2cdJkXj40wMA/Zeq06XnpM7TztGek502fNjNv1qxCaSwBzp8+e9YcMeG58+bPmyssNmfW7AXFxQsWLlq8ZCkqYOBftlxJfkXqylWrV61MXSGvtHzN2rVr1q3fsHETKmAQMNLdHLtFefP8rfM3K2+J3axrJCBgtG37DgyFO3ft2rlz1+6dO/fs3LkbxATjvfuWolvtTCRg4CMSMHQTCRh4iAQM5UQCBi4iAQM7kYAhgkjAwEokID7AmYgEDFpEAgYpIgEA2hc6qEvsr/QAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MTUrMDI6MDDoZk8yAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjE1KzAyOjAwmTv3jgAAAABJRU5ErkJggg==", "name": {"common": "United States", "deu": "Vereinigte Staaten von Amerika", "fra": "États-Unis", "hrv": "Sjedinjene Američke Države", "ita": "Stati Uniti d'America", "jpn": "アメリカ合衆国", "nld": "Verenigde Staten", "por": "Estados Unidos", "rus": "Соединённые Штаты Америки", "spa": "Estados Unidos", "svk": "Spojené štáty", "fin": "Yhdysvallat", "zho": "美国", "isr": "ארצות הברית"}}, "UM": {"currency": "USD", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAj1QTFRF4bm96r/B6b/B3a6y5bO15bK1wltjyF1j8unr+vDx+vDwyHB2z3J31ZSZ3JicGS9dJTplITdjGjBdKDxnHjNhGzFfKT1oHzZjQjJXtzxFvT1EGjBedYKdXWyNFStaIDVih5OrRFV7FCpZLkJsjpmvdYKeGC5cWmyNQDFWuD5Hvj9GM0dwRlh9NEdwOEtzLEBqTF2BLUFrOUx0M0dvPVB2KT9pTFJ1HDJfeIWgFy1bJjpmhI+oNklxFi1bS12BY3KRFixbUmOF9/f5////aHaUVGSGIjdjPlF3Gi9dfYqkU2SGaHeVUGKFSk5ySlt/PlB3MkZvMUVuKz9qV2eJKz9pO093QDFVtjpCvDpBFSxbgo6nUWKEEilYJjtmj5qwOUxzEihYFCtaa3mXFCxbRDpfRFZ7KDxoJzxnXW2NKT5pYXCQQFJ5UF6BTl+DHjNgcH6aO051HjRhYnGQS1yAUGCD9fT1/vv8/vv7Fy1cfYmjT1+DiZSsZ3aUFS1cRkBkO011M0ZvMERtQVN5QlR6LEBrQ1V7Ok10N0pyL0VvQTBVtTc/uzc+coCcW2qLhJCpLkJri5atc4CcWGmLR0drHzRhITZiaXiWRVd8c4GdIDZiWGiKJTpmPE92YG+PL0NtKj5pJztnIDZjTlh76c/S8dbX8dXXdoOfXm2NiZWsEylZL0JskJuxd4SfFy5cW22OQDNXukZOwUZNwEZNJDllPVB3P1F4MkdwSzxfu0ZOzdLczNHbzdHbzNDb1tPb8NXXWNrergAAAA90Uk5T/v7+/v7+/v7+/v7+/v7+6a2FXwAAAAFiS0dEQ2fQDWIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAITSURBVDjLY+AXEOQXEhbhFxUT5RcRFuIXFODnFxCXkJRCAwzSMrJy8gqKSsoqykqKCvJysqpqaqrqGppaaICBX1tZR1dP38DQyNBAX09XR9nYxMTY1IyBkQkVMPDzm1sYWFpZ29jaWFtZ2lmYq9nbqzk4OjmjAQZpF1dpNwt3D0NPQw93CzdpL29zc28fX2YWVlTAwO/nryzmFRAYFBwUGOAlpuzvFxLiFxoWHoEGGPgjpaOiY2Lj4hPi42JjoqOkE5OS5JJT2NjRAIOQY2paeoZNpn6WfqZNRnpaqmNsrGN2DgcnFypg4M/KzXPLLyjUV9UvLMh3y8stKiwsKi4pLStHBQz8FaKVVYmx1XpKetWxiVWVog41Nba1ddw8aICBv77Bv7HJsLmltaXZsKnRv6Gtvb2to7OrGw0w8Pf0ysn3NSf2T+hPbO6Tl+udqKY2cdJkXj40wMA/Zeq06XnpM7TztGek502fNjNv1qxCaSwBzp8+e9YcMeG58+bPmyssNmfW7AXFxQsWLlq8ZCkqYOBftlxJfkXqylWrV61MXSGvtHzN2rVr1q3fsHETKmAQMNLdHLtFefP8rfM3K2+J3axrJCBgtG37DgyFO3ft2rlz1+6dO/fs3LkbxATjvfuWolvtTCRg4CMSMHQTCRh4iAQM5UQCBi4iAQM7kYAhgkjAwEokID7AmYgEDFpEAgYpIgEA2hc6qEvsr/QAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTU6MDIrMDI6MDDha3EiAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE1OjAyKzAyOjAwkDbJngAAAABJRU5ErkJggg==", "name": {"common": "United States Minor Outlying Islands", "deu": "Kleinere Inselbesitzungen der Vereinigten Staaten", "fra": "Îles mineures éloignées des États-Unis", "hrv": "Mali udaljeni otoci SAD-a", "ita": "Isole minori esterne degli Stati Uniti d'America", "jpn": "合衆国領有小離島", "nld": "Kleine afgelegen eilanden van de Verenigde Staten", "por": "Ilhas Menores Distantes dos Estados Unidos", "rus": "Внешние малые острова США", "spa": "Islas Ultramarinas Menores de Estados Unidos", "svk": "Menšie odľahlé ostrovy USA", "fin": "Yhdysvaltain asumattomat saaret", "zho": "美国本土外小岛屿", "isr": "האיים המרוחקים הקטנים של ארה״ב"}}, "VI": {"currency": "USD", "callingCode": "1340", "flag": "data:image/png;base64,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", "name": {"common": "United States Virgin Islands", "deu": "Amerikanische Jungferninseln", "fra": "Îles Vierges des États-Unis", "hrv": "Američki Djevičanski Otoci", "ita": "Isole Vergini americane", "jpn": "アメリカ領ヴァージン諸島", "nld": "Amerikaanse Maagdeneilanden", "por": "Ilhas Virgens dos Estados Unidos", "rus": "Виргинские Острова", "spa": "Islas Vírgenes de los Estados Unidos", "svk": "Americké Panenské ostrovy", "fin": "Neitsytsaaret", "zho": "美属维尔京群岛", "isr": "איי הבתולה של ארצות הברית"}}, "UY": {"currency": "UYI", "callingCode": "598", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAiVQTFRF/////v35///+/v36/v789uis/Pfj9vDN+vXg/v76+Oy8/Pnr/Prx//7+9+mi+Oup8+iP9+y2+vLI9eOH/Pru//790NjrcYjDb4bC/v378uip9+24+O+u9+uJ8uVT8t1l+OuA+O+y9Oyk9Ou+q7jbAyiSACWR+fDN+e6++fTM8eFS8ddH9ehe8eQd8dUs9e1k8uZM8d1o/Pnt/Pnp/fnsAyiTACaRACaS+/Xd9uaY9+iJ9e5+8eAR8eES9fQH9O8M8ekP8NMc9uh7+OuP9eOK+O/IvsjjOlmrOFir/Pjo9ueR9OJW8ugj+f0A+v8A9/YC8ugv9e5o9++j+vTR/v34+/v98vT69OzE8eOB8N5H8NkV8+kH+f4B8eEJ79gc8N5U8eSQ9vHU/fvx+fHB9u2W9e9g8uUk+PoA9vgC8ug09uli9+6m/fvz+Pn86u32/v779umx9uWN+O2a9OJr8NUP8uwY9OwJ9PIO8NsV8eMc9vGR9+uH9+mb/PjmucThLE6mKkyl/fru/vz2+vXd8NxR8+ti9vBk8NQc8uw49u5l8NlN8eRn+vbX+fHF+/biAymTACeS8uiu9/G6+O+f9++K8dtR9Ox++PCF+fTB9uy49Oy//fz2+/bb9uaE/Pjj8+aY9/C3+O2d+O+w/fv11t3ug5fLgpbK+/fh+fLO9/HU+vfl+/ff+O++///9oK/XACmTACiSFz2d2N7u+fr9SmeyWnW5/v7/+P+5hQAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFASURBVDjLldRFV0MxEAXg4bV4ixd3SnGH4u4W3N2d4u7u7u7u9vt4hW0K02+Xk7u5czIBkI8Cw+FichxFJWUVVTX1/2I8voamlraOrh5PAKBvYEgjDRoZm5iamVtYWlnbsCehrYiGvbGzd3B0cnZxdXP38PTyBqGP2JeCDfr5BwQGBYeEhoVHREZFQ0xsHA0b5MYnJCYlE5KSmpaekQlZ2VRslZzcvPwCQkhhUXFJaRkju3R5RWVVdQ0htXX1DY0ATc1UAJKW1rb2js6u7p7evv6BQRgaHqEBYEbHxicmp6ZnZufmFxaXQLi8QvMzx9W19Y3Nre2d3T3pHGWOByT7B4dHxyenZ3DOB7i4vKL57XN9A7d39w+o9yN4BObPwBMSPCPBCxK8IsEbEn6t3pHgAwnESCBCgk8k+EKS8+tB+Ab8+Iwkca/RVwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1NCswMjowMCWZIUIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTQrMDI6MDBUxJn+AAAAAElFTkSuQmCC", "name": {"common": "Uruguay", "deu": "Uruguay", "fra": "Uruguay", "hrv": "Urugvaj", "ita": "Uruguay", "jpn": "ウルグアイ", "nld": "Uruguay", "por": "Uruguai", "rus": "Уругвай", "spa": "Uruguay", "svk": "Uruguaj", "fin": "Uruguay", "zho": "乌拉圭", "isr": "אורוגוואי"}}, "UZ": {"currency": "UZS", "callingCode": "998", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAYxQTFRFlKzUjafRo7jaw9DnxNHnqLvckqrTkKnSj6jSkqvTkanTkarTlazUyNXp////8fT5zNjrucnjnLLXk6vTnbPYqbzcqr3dmrHXpLjaorfalq3Vv83l2+PxiqTQjqjSlq7Vs8Tg1+Hv2uPwqb3dxtPow9Hnla3Vla3U8PP5/P3+mrHWl67Vk6vUjqfRn7XY5ev1mbDWztrsydbqprrbusnjm7HX+/z+7vL4mK/Wwc/mmK/VpbnbvMzkvczlsMLfscPg3OTxscLgiKPPk6zUnLPXlavSkajQo7XX9/j6/Pz9usfgma7TkafQ19/stsXfk6nQ4+jxoLPWr7/c3OPu3+XvqLrZxdDlytTnjaXOlqvSlKrRk7bllLflj7Pjnr/q1er96/v/z+X8qcjukrbknL7ol7rmk7bknr/plLjllrnmnb7onb7plbjmmbvnmrznk7flmniCm3mCl3aBknJ+lnWAmXeBmHaBmXiBmHeB5aiR5KiR5amR5qmS/vz7/f38vnhNB5UAAK4DAagAAKkAcIAQIwAAAAFiS0dEDm+9ME8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAElSURBVDjLY2BgYGBkYmZhZQADNnYGDk4ubgZsgIedl49fQFBIGMjmFBHlFBOXkGTDok6KUZpPhltWDsSW51BQ5FBSVlFlVMOiUl1DU4tdWx3I0tHV4ZZjkGNjZ8dmM7cenz47yAB1HQNDAzFeDiNjQRMOdUyFbKZm5mwgcU42C0sraxt2W0k7e3YsCtU5HPgcnRicdVwsdLR5eOQZtBnUpLBZ7erm7uHp5e3t4+vnHxAYFOwTEhoWHhEZhQ4YomNi4+ITEpOSU1LT0jMys5Kzc3LzkvMxAENBQWFRcUlpWUFZaUFRAYgur6gowAQMlZVV1TXVlZWV1VAEYmMBDHxEAoZaIgHxJg4FhXVEAuJNrCcSMDQQCRgaiQQMTUQChmYiwbBSCACCMlF7L3AvtgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDoyNyswMjowMB60MsYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6MjcrMDI6MDBv6Yp6AAAAAElFTkSuQmCC", "name": {"common": "Uzbekistan", "deu": "Usbekistan", "fra": "Ouzbékistan", "hrv": "Uzbekistan", "ita": "Uzbekistan", "jpn": "ウズベキスタン", "nld": "Oezbekistan", "por": "Uzbequistão", "rus": "Узбекистан", "spa": "Uzbekistán", "svk": "Uzbekistan", "fin": "Uzbekistan", "zho": "乌兹别克斯坦", "isr": "אוזבקיסטן"}}, "VU": {"currency": "VUV", "callingCode": "678", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAApFQTFRFQgQAFTcUMYYzogAA0AAAzwAAzgAAiosAPyQAdQAAwwAA0QAA+/sA1dcAYV4ARQoAnQAAYmIAz88A+PgApacAQS4AZwEAvAAAAAAAHBwAiIgA7u4A4eMAb28AQA8AkgAAzAAAAwMAPT0At7cAtbgARzoAWwIAtQAADg4AaGgA3d0A6usAf4AAPBYAhQAAyQAAJiYAmJgA8vIAxMcAUEkAUQQArAAABQUASkoAxcUA7/AAj5IAPB8AeAAAxAAAFBQAeHgA5eUA0tQAXFkASAgAoQAAAQEAMDAAqakAoKMAPyoAbAAAvwAAIiIAVlYAZGQAS0sAEhIACQkAWVkA0dEA3N0AamkAQQ0AlgAAzQAAjIwAVVUAQkIAh4cAICAAiYkA5+cAsLMARTYAYAEAtgAAywAACwsAjY0ANDQAVFQADAwARUUAe3sAuLgA4eIAenoAOBMAQAAAQQAAUlIArKwAkJAADw8ADQ0A4+MAyMoAoKQAoaQAFRUAGBgAIyMAUFAAMzMAaWkA5OQAycgAoqAAo6EAY2MAjo4ANzcAUVEAfX0AX18APj4AubkA4eEAeXkAHzIMFDYUQUEAf38AmpoAi4sAHR0AiooAsrAAOkIFHU8eOJc5P6g/Hh4AMTEAWloA0tIA3NsAaWoBHDgRLXwwQKlAQKtAQKpAqqoAoaAAMDsHIFkjO548QaxB09IAWVsCGz0VMIUzBgYAxsYA7+8AkI8AKDcJI2MnPaI9mZkAxsQAS08DG0QZNI426+oAf34AITYMJ24rPqY/trUAPkUEHEwdN5U54uEAbm4BHTcQK3guP6lAQatB0NAApqQAMz0GH1UhOps71tUAXmABGzsTL4IyiokALDoJImAlPKE9////Sn83tQAAAAN0Uk5T/v7+lRaJLAAAAAFiS0dE2u4DJoIAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAGuSURBVDjLY2BgZmFlIwIwsHNwcnETo5CHl49fgI2woQyCQsIiomLiBA1lkJCUkpaRlZNXYCGkUEJRSVlYRVVNnZuVgEIJCQ1NLW0dXT19FkIKJSQMDI2MTUzN8IQUVKGEuYWllbWNrR03IYUSEvYOjk7OLq6srIQUSri5e0h7enn7cONW6OvnHxAIpIOCQ0LDwiMiWXAoVIqKjvGPjQNpkYxPSExKTlFIxQBAhWnpBhmZWdk5biCVikq5efkFhUUYAKgwLkqxuEQjsLQM4tRyzYrKqmoMAFRYY1hbVy/ZkB4EVdjY1NzSigFAbmxrV+ro7OoGK1Ps6e3rnzARE4AUmk+aPGWqbBZI3bTpCTNmzpo9BxNAwnGu3zxzUPDMX7Bw0eIlS5ctxwTIAT5vhfTKVavXrF2+HK/Cmn7Hdes3bFy2bDlehZsCNm/Zum37DuzGIRQa7DTatXvP3rXLluNVqNGotW//gYOHcBoHUQjKCoePHD2GTxlI4bR46eMnTp46fWY5foVtZ4XPnb9wEb9xIIU8ly5fubp8GSF1yxmuXb9xk6BxIIWMTGsJGwcEAK2Qk7NAptKcAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE0OjQwKzAyOjAwHXwFzwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNDo0MCswMjowMGwhvXMAAAAASUVORK5CYII=", "name": {"common": "Vanuatu", "deu": "Vanuatu", "fra": "Vanuatu", "hrv": "Vanuatu", "ita": "Vanuatu", "jpn": "バヌアツ", "nld": "Vanuatu", "por": "Vanuatu", "rus": "Вануату", "spa": "Vanuatu", "svk": "Vanuatu", "fin": "Vanuatu", "zho": "瓦努阿图", "isr": "ונואטו"}}, "VA": {"currency": "EUR", "callingCode": "3906698", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAnlQTFRF/+AA/+AB/98A/+IR//3u///////++vfC/v75/v7//Pvx/v7+///9//7+/fzz+/nm/PjD+/ne/Pvw+fjg9/Go+PCI9/GZ+PTG//3t/fzv/v789fTu+fr++fbS9++p9+2M9++f9/K8/P3/9/by/v79+/v5/Pz7//zs9vLB3dmn6unf8fH59PPW/f7G/f+j/f+6+PbH8fH16+vn5uTG4N6++Pj4//zt8/Pyt7au2da8+fv67ufS4Z5d3IVC4ZRL7dqu9vr/8O/k6Nw39e6D3t7ivLy6xsbLv72v1dO29PH168S44K2s4rev9Ozz6urN59tF8Odu9ulB9etm+/ng///vzs/TsrKxz8/P1dTN0M662Ne36O7Q/P/97/bY7eqb49+t6eOG9OyA8udR8eQt/PnS4ODfubm45eXg8/Pz9vbyxcbU18XK9+dh9O+h9vf/8PH+7eNY9OlQ/Pzz/v//7+/x9fb+9vb7+fn/+frKrlocyKaG9/3/9/f8+vjb/v3q+Pn9/Pzy+PS/9/Xd9fCk+Okqv2dBtZKjx8zG8e/k5+fn7/D1///0/v73/vzs4+G/6uBY7+dy7uNl5d5L3reUxHV+15ydy8GjwcGryMfKxMTHzsue/fzp//3r8Ohw9vK86txm58mqz5Ka7MrJvqyu5+vuwsLH7Ozv7OR79/K09OHf3sNL/f/ny4eO9+jozdHJzrCt+/HxxMTC7e3s4eDZ3ddw7d5hz5EtyLF77+r0yHx568/W6NiUs4Rrsn6Ew8jG2tSJ//71/f3+/Pz6/Prg8+Pa687U7tTTvmlp4ba28NbX9ufn9PX19/f3///6vXBw7djY/v39+fPz/Pn5CdrmUAAAAAFiS0dEBfhv6ccAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAFWSURBVDjLY2BAB4xMzCysbKzogGGEKGRjZecgzkROLm6CCnlYeVn5+AUEheCOwGUiGxursIiomDiYhVOhBKuklLSMrJy8gqKSsoqqGk6F6qwamlraOrp6+gaGRsYmpjgVmrGaW1haWdvY2tk7ODo5g92J3Y0urm7uHp5e3j6+fv4BgUHBQKVYFYaEhoVHREZFx8TGxSckJiWnAEMBh2dS09IzMrOyc3Lz8gsKi0B2Y1dYzFZSWlZeUVlVnVFTWwc0D2c41jc0NjW3tLa1d3R2dffg9HUva1//hImTJk+ZOm36jJmzZuO0eg7r3HmsrPMXLFy0eAkr69JlrNw4FS5fwbpy1eo1a9et38C6cRNOhb2sm7ds3bZ9x85du/fs3bf/AJ5EcfDQ4SNHjx0/cfLU6TNn2fAlM6AkL+u586zcBBMum8qFCxcv8XJzE5HCic9cvIMjXwMAtnNhztqhnUkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6NDgrMDI6MDAuk0uoAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjQ4KzAyOjAwX87zFAAAAABJRU5ErkJggg==", "name": {"common": "Vatican City", "deu": "Vatikanstadt", "fra": "Cité du Vatican", "hrv": "Vatikan", "ita": "Città del Vaticano", "jpn": "バチカン市国", "nld": "Vaticaanstad", "por": "Cidade do Vaticano", "rus": "Ватикан", "spa": "Ciudad del Vaticano", "svk": "Vatikán", "fin": "Vatikaani", "zho": "梵蒂冈", "isr": "הוותיקן"}}, "VE": {"currency": "VEF", "callingCode": "58", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAANVQTFRF/8sA/80A4LMWHxikHhijHRejHxijAAC7AgK8EBDAAAC8AQG8LS7IMTHJDw/AAwO8AAC6OzvKoKDlIyPEAgK7hobejo7gBAS7Hh7Cnp7lQkLMBga8EhK/JyfFcHDYFhbAHR3CHx/Cbm7YLCzGAAC5ERG/Bwe8UVHQFxfAAQG6ExO/mprkWVnSMjLHY2PVDAy9CQm8YGDUNzfJGRnBBQW7MTHHr6/pMzPILS3GODjJGBjCW1zUGRnCFRbBWlzUGxzDGgCkGAGjGwCkwQAW3gAA3AAA////C5QEIwAAAAFiS0dERhe6+e0AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACvSURBVDjLY2AYBVQBjEQCBiYiAQMzGmBhZmZlZQNRqICBHRVwcHJx8/BysPPxo0kwCKACQSFhEVExcQlJKTQJFIXSMgKycvICCooCMkrKKqpqOBWqS2oIaGqCWEBSS1sHl0IVXT19BM/A0EgFu0JxYxNNZDM0xY3FsSlUMTUzRzZCQMXCzBIhgBQ8VtY2qCFia2ePFDwOCODogAqckAUYnIkEDC5EAgZXIsGoQrwAAMoOjbEqrfOHAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDEzLTEwLTA3VDEzOjE1OjE3KzAyOjAwf/leGwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMy0xMC0wN1QxMzoxNToxNyswMjowMA6k5qcAAAAASUVORK5CYII=", "name": {"common": "Venezuela", "deu": "Venezuela", "fra": "Venezuela", "hrv": "Venezuela", "ita": "Venezuela", "jpn": "ベネズエラ・ボリバル共和国", "nld": "Venezuela", "por": "Venezuela", "rus": "Венесуэла", "spa": "Venezuela", "svk": "Venezuela", "fin": "Venezuela", "zho": "委内瑞拉", "isr": "ונצואלה"}}, "VN": {"currency": "VND", "callingCode": "84", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAPZQTFRF7AAV7AEV7AkV7RQU7AIV6wAW8DoQ9YAL7AMV7AQV6gAX9XoL/N4D+rsG//8A7ywS7h4T/vAB9XcL7AYU7AAW818O+8QF6wEW7AUV7yYS7hoT7iES7RQT+bIG//4A/vgB8UAQ7h0T7yUS7yMS7h4S8DAR7hkT9X0L//kA/vQB//oA//0A//wA//sA7hwT8lMO95YJ7iET+8kE/vYB810N6wAX95UJ/NsD7y4R7zkR+KoI7QYU9oYK/uwC+8oE/NQE+KEI8k8P7ygS//kB+bMG81wN95gJ9G8M9XYL/NUD/eYC7AcV+KMI+J8I8UYP6wUW7RIT////RWZ05wAAAAFiS0dEUZRpfCoAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAD7SURBVDjL3ZFZN8RAFIQTuUW6USaW0QYJYxsi9tjGMmPf+f+/Rseb43TLo6Ne+j5859atriD4WwrrcnXBgajWTsHgEOIaoNLDI6hjLBjlGMSfQkTioJFwHBN2FOelwCSUniKbmIaVizMzrVZjdm6eabaw2F5aXll1pV9jZ72zkZNpvllkbG45wBDbCXOmZIVyx8B1ZIzdPUtWHPcPytgdXcEcVhtzHh2XylePwYl1LQqe6q6/FpwxS1nw/Ne+L8jLniX73r4FV9e8ifRtwjsvqMp7PkBM2X58evZ5h8FLXyuxp6rXN+/KboSvX5Hg/cOfJoy/vx7zn9P/0ydsjBUrBoCy/wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo0MSswMjowMLsLDnsAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NDErMDI6MDDKVrbHAAAAAElFTkSuQmCC", "name": {"common": "Vietnam", "deu": "Vietnam", "fra": "Viêt Nam", "hrv": "Vijetnam", "ita": "Vietnam", "jpn": "ベトナム", "nld": "Vietnam", "por": "Vietname", "rus": "Вьетнам", "spa": "Vietnam", "svk": "Vietnam", "fin": "Vietnam", "zho": "越南", "isr": "וייטנאם"}}, "WF": {"currency": "XPF", "callingCode": "681", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRFACZ/AymBABl3qbbU////+/z9//z8+7G58gok8xsz8xgwxtlaVAAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAeSURBVCjPY2AAAUZlVxcgSKmYtQoEGEYFRwVpKQgAg3q7n/McyNsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MjcrMDI6MDAetDLGAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjI3KzAyOjAwb+mKegAAAABJRU5ErkJggg==", "name": {"common": "Wallis and Futuna", "deu": "Wallis und Futuna", "fra": "Wallis-et-Futuna", "hrv": "Wallis i Fortuna", "ita": "Wallis e Futuna", "jpn": "ウォリス・フツナ", "nld": "Wallis en Futuna", "por": "Wallis e Futuna", "rus": "Уоллис и Футуна", "spa": "Wallis y Futuna", "svk": "Wallis a Futuna", "fin": "Wallis ja Futuna", "zho": "瓦利斯和富图纳群岛", "isr": "איי ווליס ופוטונה"}}, "EH": {"currency": "MAD", "callingCode": "212", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAiJQTFRFAwAAAQAAAAAAqgAAGAAABAAA/wAA5gAAQgAAAgAA+wAA/gAAgQAA/QAAvwAAHAAA7AAATgAA/AAAjwAAygAAJQAAAgEBAgIC8wAAWwAA/wEBmgAAHRsbHysrHxoaHx8fHxsbHx0cHyAg/AIC+iUl+cvL+f//+fb2+ff3+fn5+fj4+fb3/AEB/wgI/5iY//////39//7+///+/52l/yw1/1lb/+Li/gIC/gMD/lVV/u/v/v///vr6/v7+/vz8/nFz/kVE/vLx/vPy/vPx/wQE/yEh/8bG//z8/8jJ/w4P/+Di/7Su/6Ob//v7/wMD/3x8//r6/4mJ/zxC/+/u/4h8/zoo/zIh/35w/+nm/wIC/21t/4yM/zo//9HO/xII/wUA/725/xgY/7m5/8/Q/wgJ/9fY/8nD/4t+/5KH/7my//38/0hI/+jo/4KC/zU1/+bm//Tz//Hw/AIB/QMC/YiH/f///f39/fv6/f79/fz8/bOz/T5E/VJY/d7e/fr4/f7+/gEA/AMB/yYr/8vQ/QEA/AEA/wABqSgAR5EyTapPTJc7TJw/TJxATJk9TJk8+wIA9wQAb0oAAoMAA3cABHAABHQA1BYANmcAAIQAE3kAE3sCE30DE3wDnjIAEHsABoAAFXkA8AgAX1IAD3wAxx0AKm4AAIMAFHkAEXoA/AIAjToACX8ACH8A6AwAUFoAAIUAEXsAE3oAsigAI3EAAYMACn4Az0nTXQAAAAFiS0dELyPUIBEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAG7SURBVDjLY2BgZGAiBjAwszCxMhKjkI2dg4mVkwiFXNxsPIxEWM/AxsvFxsdP2HoGNjZuLjYBQSYGToIK2diEeNmEWZlYCSsEWS8iysQqJo4bQBSysXGxSUgyMRI0EWy9lLSMrJw8DCgoKinJIwG4QjZuZTYVVTV1DU0Q0NJSU9NW19TShAOEQjY2HTZdPX0DQ30gMNI3NjE10zfQhwNkhWzmFtyWVtY2trZ21vbcDo7WTs62cICikE3Khc3VTd/AXd+DzdNLX9/bR98Xq4lA4Ccl5a8foB/IFqQfHBIaFh6hb4hDYSRbFNCYaLYYff3YuPgEuDMxrE5M0jcw0E9mS0nVT0vPyNTPwqYw0o8tO0ff1xBoTC5bXn5BgGEhVl8X8RaXlJaVVwBBZWlVdU1taV19BQwgFDY0sjU1I4ccKoArbGlta+/o7OrugYLePjgTDGAK+9kmTJw0ecpUnACisKGfbdr0GTNnzZ4zGxcAK2xtYZs7b/6CefgAKCv0sy1cNGPxzHkEFLb0sy1Zumz5inkEFK5sYFu1es3yeYQAA9vades3bCSobh7Dps1blm8grG4ew5qthK0FAQAMqy9yC7XdrgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1OCswMjowMOI5SzYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTgrMDI6MDCTZPOKAAAAAElFTkSuQmCC", "name": {"common": "Western Sahara", "deu": "Westsahara", "fra": "Sahara Occidental", "hrv": "Zapadna Sahara", "ita": "Sahara Occidentale", "jpn": "西サハラ", "nld": "Westelijke Sahara", "por": "Saara Ocidental", "rus": "Западная Сахара", "spa": "Sahara Occidental", "svk": "Západná Sahara", "fin": "Länsi-Sahara", "zho": "西撒哈拉", "isr": "סהרה המערבית"}}, "YE": {"currency": "YER", "callingCode": "967", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeBAMAAACs80HuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABhQTFRF8QYA8QgC8AAA9DQv////Ly8vAAAAAgICaSnPYAAAAAFiS0dEBI9o2VEAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAAsSURBVCjPY2AYfEAQC2BQwgIYjLEABhcsYGgLhmIBDGlYAEM5FoBd5YAKAgC024zJhQVl1gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNTowOSswMjowMONsJdgAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MDkrMDI6MDCSMZ1kAAAAAElFTkSuQmCC", "name": {"common": "Yemen", "deu": "Jemen", "fra": "Yémen", "hrv": "Jemen", "ita": "Yemen", "jpn": "イエメン", "nld": "Jemen", "por": "Iémen", "rus": "Йемен", "spa": "Yemen", "svk": "Jemen", "fin": "Jemen", "zho": "也门", "isr": "תימן"}}, "ZM": {"currency": "ZMW", "callingCode": "260", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAelQTFRFGYoAGooAGYsAFYwAHIoAG4kAG4sAG4oAHIkAGIwAGIsAGYcAK4AAFYkADosAD4wAF4sAFIsAEocAEIwADY0AG4MAG4cAEY4ARmwAuV8AonMAemwATX4AGogAPoEArGwAM3YADI0AIYcARH8AXHQAfnIAp2IATG8AFosAKYEAg2kA12QA4moAuVwAtGoAzHMA73sAl20Al28Ar2cAvF8A0GQA7GsAxGMAL3sAFY0ADYoAT3EAo2kAwmMAyl4A0GYA6XoA3W0AvV0At14AxGAAyGwAnXAAQXwAEYwAGokAEI0AFIYAPnsAX28AlGoAxGkAx24AvmAAplMAbGgAKIAAEowAGosAEY0ADo0AEIkAkGYAZ2gANnwAEIoAHogAJYAAiGYAf2EAKHsADY4AH4kAHYkAEI8AD48AFZAAHJAAG5IAGY4ADooAD4sAQnQDSHEERnIERnEESnIEKm0CD2gAE2sAFGoAEGcAI3EATogAS4cAIIYBzygP6hoR4x0Q4RwQ9R8SZg0HAAAABAAAAwIARiQA/4MA9HsA9n0AyCwO4h4Q3CEQ2SAQ7SMRYxAHBAIAAwMARCUA+YMA63wA7X0A7n0AySsO3SEQ2iAQ7iIRYw8HBAEARCQA+4MA7HwA730A2h8Q7XwA////hemiEQAAAAFiS0dEorDd34wAAAAJcEhZcwAAAEgAAABIAEbJaz4AAAEHSURBVDjLY2DABRgZiANMzIwsrAyMbEAd7BzsnFw41XHz8PLxCwgKsbMLCIvwiYrhUMcqLiEpJS0jKyevoKikrKKqpo7LgRqaWto6unr6BoZGxiamZuYWOL1iaWVtY2tn7+Do5Ozi6ubugdMznuxe3j6+fv4BgUHBIVah7Hj8zRoWHhEZHBUdE2sJ9D9uZUAQxxifkJiUnJLKzo7bxLR0IMjITOHMys7J5ePjw2lgXn5BYVFxSWlZeUVlFRDg8LNAdU1tXX1DY1NzS1NrW3tHRwdOhZ1d3T29fU39E5omTpo8ZepUnAqn1U2fMXNW0+yWpjlz582fPx+fwgWjCkcVUq5wISkmAgDy89vmc0Ci5gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNDo1MCswMjowMNHWBVEAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTQ6NTArMDI6MDCgi73tAAAAAElFTkSuQmCC", "name": {"common": "Zambia", "deu": "Sambia", "fra": "Zambie", "hrv": "Zambija", "ita": "Zambia", "jpn": "ザンビア", "nld": "Zambia", "por": "Zâmbia", "rus": "Замбия", "spa": "Zambia", "svk": "Zambia", "fin": "Sambia", "zho": "赞比亚", "isr": "זמביה"}}, "ZW": {"currency": "ZWL", "callingCode": "263", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAuhQTFRFnKICwcEDvr4DrXMC8YUH8oUHrxkMsBkNDAMDCwEADAIBrnMCwsEDv74DoKCgIychFkgBL40HMpQIMZII/v7+xMTEPkE9ETcCKoAG////5eXma2prEicKImoEMZMI+Pj4nJucIioeGE4BMo0HNpUINJMIyMfIQENBKTwC5+fob25vPzQLu5gA/tQA/9QA/9MA9/DwnZyeNDAgiG8A88kA/9IA+vr3/f388Kyo+uTjyszNSEdEWUkC2rQA/9YA/9UA8OzN2sxJ6OCJ+Ovo4FdP8rm16enqcHFzOjIM+Pj23dJz6NEC8NwN4q5v1yoj6IN9+fn6oaKkMiMkcgwF0x0P4B8Q3h4Q9fXp6dpJ9N4A1p8Kzi0U3k5I/PDvzdDQSklJSwsFvBoM4SAQ3iAQ/fPy+ufl++vq/O3x4dB65M0A7NUA4aoE0lgd8Lu4++np++jn++no/uvq6dvadXZ2MxIPmRQJ+uPh53123EU73EU/14Ij8twA9NwA+OIA7MwC14Ug10c33EA520A34FlR9L67+vr5pqioMycmbA0F++rp1Skg14II7NgA5M4A8doA+OEA9NsA3JUG0DkV4FpU9cvI//390NHRTExM//r76q+g2q8N69YA79gA074A18IA6NEA7NYA3bUP59Sr7u/veHl5BwcHAAAA6uWr38oG6dIA2cQAw7AAzbkA4csA4swA2skZ5uO87u7u4NiK1sEAyLUA0LwAxLEA3ccA2stF+fn2TEtL7em+6tUKyrYAxbIAx7MAyLQAx7QA1cx2/v//+vr6paio8d3M5MAS3McD5M4E5c8D480B4cwA2b4A3LB6//7/6+vrdXd3MxEP66Kc3ZJl6eKv7eex7ues7eak7OWZ5ct44Hpj/fDwzc/PSkhI//z899XT/fPz89PRnpGSMiQk3h8QcXJ1ysrLR0dE27QA+fn5nZ6g5+fnKjwCnJqcISoeGE8B5eXlamlrxMPEEjcCoJ+gIichL40I9LVm3wAAAA50Uk5T/v7+/v7+/v7+/v7+/v7BShreAAAAAWJLR0QZ7G61iAAAAAlwSFlzAAAASAAAAEgARslrPgAAAatJREFUOMtj4OMXEBQSJgwYRETFxCWIUMkgKSklLSMrJ0eEQkl5BUUlZRVV/ACkUFJSTV2DgZEJL4AolNTU0tbR1dPHA6AKJQ0MjYxN9ExxA7BCM3NJC0sraxtbO3ucAKTQwdHJ2cVV0s3dg5mFlQUHACr09PL28fXzl5QMCAwKDgkNww6ACkXCIyKjomNigWbHxSckJiVjBQwpqWnpGZlZ2Tm5efkFhUXFJaXJSdgAQ1l5RWVVdU1tXX1DY1NzS2tbewcbOxbAINnp39Xd09vXP2HipMlTpkpKTpvOwcmFCYBunDFz1uw5c+fNX7BwEThIFy9ZugwTgIJn+YqVq1avWbtu/Qawwo04FW7avGXrttWbt+/YCVI3bRcuqyV379m7b/++AwcPHZaUPHIUp2ckJY8dP3Hy1OkzZ8+dl7xw8RKu4AEqvHzl6rXrN27eun3n7j3cAS4pef/BQ0gKevT4CTAKn2IHsGQGBG7PPLjxJgooeP7C5iWBZAYCr14Tk3AlJd8QmRXU1N8y8PASzFzy795/ICa7fvxEVAEg8lnsC1FFytdvAt+JKaQAum8igK+St78AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTAtMDdUMTM6MTQ6MjQrMDI6MDAvXChbAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTEwLTA3VDEzOjE0OjI0KzAyOjAwXgGQ5wAAAABJRU5ErkJggg==", "name": {"common": "Zimbabwe", "deu": "Simbabwe", "fra": "Zimbabwe", "hrv": "Zimbabve", "ita": "Zimbabwe", "jpn": "ジンバブエ", "nld": "Zimbabwe", "por": "Zimbabwe", "rus": "Зимбабве", "spa": "Zimbabue", "svk": "Zimbabwe", "fin": "Zimbabwe", "zho": "津巴布韦", "isr": "זימבבואה"}}, "AX": {"currency": "EUR", "callingCode": "358", "flag": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAMAAABpA6zvAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAIRQTFRFAFOlAFOlnZ8//9IA+rkG2Cgt0g400Q0152gc/9AA9ckGPnF9AFGnAFGonZ5A9ckHPm9/AE+qbIdfxrMl/9EA+csEj5hIaoZh/88A2Ckt/84A/skB/soB+bQH5mUd/ssB3kQm3T4n1Bcy0hA00g803kUm30Um30Ula4dgaYZh+rgG////mzb/7wAAAAF0Uk5T/hrjB30AAAABYktHRCskueQIAAAACXBIWXMAAABIAAAASABGyWs+AAAAkUlEQVQ4y2NghAMmZhZWNnYOTi5uHkZMwDCqkDoKeeGAD6qQX0CQFxMwCMGBsAhEoaiYuBAmYJBAAE4WSZBCKQlsgEEaAWRkgSbKyUtjBQwKSEBRSVmFVVUBK2BgRwbKysoq7NgB8QqRTFcDW62Ow2qiPUN08GjAASLANTUwAfFROBTS4xBTKKIlqQIKcGwKGQCIhy8sc3x++AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMy0xMC0wN1QxMzoxNToxOSswMjowMC/GJUYAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTMtMTAtMDdUMTM6MTU6MTkrMDI6MDBem536AAAAAElFTkSuQmCC", "name": {"common": "Åland Islands", "deu": "Åland", "fra": "Ahvenanmaa", "hrv": "Ålandski otoci", "ita": "Isole Aland", "jpn": "オーランド諸島", "nld": "Ålandeilanden", "por": "Alândia", "rus": "Аландские острова", "spa": "Alandia", "svk": "Alandy", "fin": "Ahvenanmaa", "zho": "奥兰群岛", "isr": "איי אולנד"}}} \ No newline at end of file diff --git a/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_data_countriesemoji.json b/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_data_countriesemoji.json deleted file mode 100644 index ec7aadc6..00000000 --- a/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_data_countriesemoji.json +++ /dev/null @@ -1 +0,0 @@ -{"AF": {"currency": "AFN", "callingCode": "93", "flag": "flag-af", "name": {"common": "Afghanistan", "cym": "Affganistan", "deu": "Afghanistan", "fra": "Afghanistan", "hrv": "Afganistan", "ita": "Afghanistan", "jpn": "アフガニスタン", "nld": "Afghanistan", "por": "Afeganistão", "rus": "Афганистан", "spa": "Afganistán", "svk": "Afganistan", "fin": "Afganistan", "zho": "阿富汗", "isr": "אפגניסטן"}}, "AL": {"currency": "ALL", "callingCode": "355", "flag": "flag-al", "name": {"common": "Albania", "cym": "Albania", "deu": "Albanien", "fra": "Albanie", "hrv": "Albanija", "ita": "Albania", "jpn": "アルバニア", "nld": "Albanië", "por": "Albânia", "rus": "Албания", "spa": "Albania", "svk": "Albánsko", "fin": "Albania", "zho": "阿尔巴尼亚", "isr": "אלבניה"}}, "DZ": {"currency": "DZD", "callingCode": "213", "flag": "flag-dz", "name": {"common": "Algeria", "cym": "Algeria", "deu": "Algerien", "fra": "Algérie", "hrv": "Alžir", "ita": "Algeria", "jpn": "アルジェリア", "nld": "Algerije", "por": "Argélia", "rus": "Алжир", "spa": "Argelia", "svk": "Alžírsko", "fin": "Algeria", "zho": "阿尔及利亚", "isr": "אלג׳יריה"}}, "AS": {"currency": "USD", "callingCode": "1684", "flag": "flag-as", "name": {"common": "American Samoa", "deu": "Amerikanisch-Samoa", "fra": "Samoa américaines", "hrv": "Američka Samoa", "ita": "Samoa Americane", "jpn": "アメリカ領サモア", "nld": "Amerikaans Samoa", "por": "Samoa Americana", "rus": "Американское Самоа", "spa": "Samoa Americana", "svk": "Americká Samoa", "fin": "Amerikan Samoa", "zho": "美属萨摩亚", "isr": "סמואה האמריקנית"}}, "AD": {"currency": "EUR", "callingCode": "376", "flag": "flag-ad", "name": {"common": "Andorra", "cym": "Andorra", "deu": "Andorra", "fra": "Andorre", "hrv": "Andora", "ita": "Andorra", "jpn": "アンドラ", "nld": "Andorra", "por": "Andorra", "rus": "Андорра", "spa": "Andorra", "svk": "Andorra", "fin": "Andorra", "zho": "安道尔", "isr": "אנדורה"}}, "AO": {"currency": "AOA", "callingCode": "244", "flag": "flag-ao", "name": {"common": "Angola", "cym": "Angola", "deu": "Angola", "fra": "Angola", "hrv": "Angola", "ita": "Angola", "jpn": "アンゴラ", "nld": "Angola", "por": "Angola", "rus": "Ангола", "spa": "Angola", "svk": "Angola", "fin": "Angola", "zho": "安哥拉", "isr": "אנגולה"}}, "AI": {"currency": "XCD", "callingCode": "1264", "flag": "flag-ai", "name": {"common": "Anguilla", "deu": "Anguilla", "fra": "Anguilla", "hrv": "Angvila", "ita": "Anguilla", "jpn": "アンギラ", "nld": "Anguilla", "por": "Anguilla", "rus": "Ангилья", "spa": "Anguilla", "svk": "Anguilla", "fin": "Anguilla", "zho": "安圭拉", "isr": "אנגילה"}}, "AQ": {"flag": "flag-aq", "callingCode": "672", "name": {"common": "Antarctica", "cym": "Antarctica", "deu": "Antarktis", "fra": "Antarctique", "hrv": "Antarktika", "ita": "Antartide", "jpn": "南極", "nld": "Antarctica", "por": "Antártida", "rus": "Антарктида", "spa": "Antártida", "svk": "Antarktída", "fin": "Etelämanner", "zho": "南极洲", "isr": "אנטארקטיקה"}}, "AG": {"currency": "XCD", "callingCode": "1268", "flag": "flag-ag", "name": {"common": "Antigua and Barbuda", "cym": "Antigwa a Barbiwda", "deu": "Antigua und Barbuda", "fra": "Antigua-et-Barbuda", "hrv": "Antigva i Barbuda", "ita": "Antigua e Barbuda", "jpn": "アンティグア・バーブーダ", "nld": "Antigua en Barbuda", "por": "Antígua e Barbuda", "rus": "Антигуа и Барбуда", "spa": "Antigua y Barbuda", "svk": "Antigua a Barbuda", "fin": "Antigua ja Barbuda", "zho": "安提瓜和巴布达", "isr": "אנטיגואה וברבודה"}}, "AR": {"currency": "ARS", "callingCode": "54", "flag": "flag-ar", "name": {"common": "Argentina", "cym": "Ariannin", "deu": "Argentinien", "fra": "Argentine", "hrv": "Argentina", "ita": "Argentina", "jpn": "アルゼンチン", "nld": "Argentinië", "por": "Argentina", "rus": "Аргентина", "spa": "Argentina", "svk": "Argentína", "fin": "Argentiina", "zho": "阿根廷", "isr": "ארגנטינה"}}, "AM": {"currency": "AMD", "callingCode": "374", "flag": "flag-am", "name": {"common": "Armenia", "cym": "Armenia", "deu": "Armenien", "fra": "Arménie", "hrv": "Armenija", "ita": "Armenia", "jpn": "アルメニア", "nld": "Armenië", "por": "Arménia", "rus": "Армения", "spa": "Armenia", "svk": "Arménsko", "fin": "Armenia", "zho": "亚美尼亚", "isr": "ארמניה"}}, "AW": {"currency": "AWG", "callingCode": "297", "flag": "flag-aw", "name": {"common": "Aruba", "deu": "Aruba", "fra": "Aruba", "hrv": "Aruba", "ita": "Aruba", "jpn": "アルバ", "nld": "Aruba", "por": "Aruba", "rus": "Аруба", "spa": "Aruba", "svk": "Aruba", "fin": "Aruba", "zho": "阿鲁巴", "isr": "ארובה"}}, "AU": {"currency": "AUD", "callingCode": "61", "flag": "flag-au", "name": {"common": "Australia", "cym": "Awstralia", "deu": "Australien", "fra": "Australie", "hrv": "Australija", "ita": "Australia", "jpn": "オーストラリア", "nld": "Australië", "por": "Austrália", "rus": "Австралия", "spa": "Australia", "svk": "Austrália", "fin": "Australia", "zho": "澳大利亚", "isr": "אוסטרליה"}}, "AT": {"currency": "EUR", "callingCode": "43", "flag": "flag-at", "name": {"common": "Austria", "cym": "Awstria", "deu": "Österreich", "fra": "Autriche", "hrv": "Austrija", "ita": "Austria", "jpn": "オーストリア", "nld": "Oostenrijk", "por": "Áustria", "rus": "Австрия", "spa": "Austria", "svk": "Rakúsko", "fin": "Itävalta", "zho": "奥地利", "isr": "אוסטריה"}}, "AZ": {"currency": "AZN", "callingCode": "994", "flag": "flag-az", "name": {"common": "Azerbaijan", "cym": "Aserbaijan", "deu": "Aserbaidschan", "fra": "Azerbaïdjan", "hrv": "Azerbajdžan", "ita": "Azerbaijan", "jpn": "アゼルバイジャン", "nld": "Azerbeidzjan", "por": "Azerbeijão", "rus": "Азербайджан", "spa": "Azerbaiyán", "svk": "Azerbajdžan", "fin": "Azerbaidzan", "zho": "阿塞拜疆", "isr": "אזרבייג׳ן"}}, "BS": {"currency": "BSD", "callingCode": "1242", "flag": "flag-bs", "name": {"common": "Bahamas", "cym": "Bahamas", "deu": "Bahamas", "fra": "Bahamas", "hrv": "Bahami", "ita": "Bahamas", "jpn": "バハマ", "nld": "Bahama’s", "por": "Bahamas", "rus": "Багамские Острова", "spa": "Bahamas", "svk": "Bahamy", "fin": "Bahamasaaret", "zho": "巴哈马", "isr": "איי בהאמה"}}, "BH": {"currency": "BHD", "callingCode": "973", "flag": "flag-bh", "name": {"common": "Bahrain", "cym": "Bahrain", "deu": "Bahrain", "fra": "Bahreïn", "hrv": "Bahrein", "ita": "Bahrein", "jpn": "バーレーン", "nld": "Bahrein", "por": "Bahrein", "rus": "Бахрейн", "spa": "Bahrein", "svk": "Bahrajn", "fin": "Bahrain", "zho": "巴林", "isr": "בחריין"}}, "BD": {"currency": "BDT", "callingCode": "880", "flag": "flag-bd", "name": {"common": "Bangladesh", "cym": "Bangladesh", "deu": "Bangladesch", "fra": "Bangladesh", "hrv": "Bangladeš", "ita": "Bangladesh", "jpn": "バングラデシュ", "nld": "Bangladesh", "por": "Bangladesh", "rus": "Бангладеш", "spa": "Bangladesh", "svk": "Bangladéš", "fin": "Bangladesh", "zho": "孟加拉国", "isr": "בנגלדש"}}, "BB": {"currency": "BBD", "callingCode": "1246", "flag": "flag-bb", "name": {"common": "Barbados", "cym": "Barbados", "deu": "Barbados", "fra": "Barbade", "hrv": "Barbados", "ita": "Barbados", "jpn": "バルバドス", "nld": "Barbados", "por": "Barbados", "rus": "Барбадос", "spa": "Barbados", "svk": "Barbados", "fin": "Barbados", "zho": "巴巴多斯", "isr": "ברבדוס"}}, "BY": {"currency": "BYR", "callingCode": "375", "flag": "flag-by", "name": {"common": "Belarus", "cym": "Belarws", "deu": "Weißrussland", "fra": "Biélorussie", "hrv": "Bjelorusija", "ita": "Bielorussia", "jpn": "ベラルーシ", "nld": "Wit-Rusland", "por": "Bielorússia", "rus": "Белоруссия", "spa": "Bielorrusia", "svk": "Bielorusko", "fin": "Valko-Venäjä", "zho": "白俄罗斯", "isr": "בלארוס"}}, "BE": {"currency": "EUR", "callingCode": "32", "flag": "flag-be", "name": {"common": "Belgium", "cym": "Gwlad Belg", "deu": "Belgien", "fra": "Belgique", "hrv": "Belgija", "ita": "Belgio", "jpn": "ベルギー", "nld": "België", "por": "Bélgica", "rus": "Бельгия", "spa": "Bélgica", "svk": "Belgicko", "fin": "Belgia", "zho": "比利时", "isr": "בלגיה"}}, "BZ": {"currency": "BZD", "callingCode": "501", "flag": "flag-bz", "name": {"common": "Belize", "cym": "Belize", "deu": "Belize", "fra": "Belize", "hrv": "Belize", "ita": "Belize", "jpn": "ベリーズ", "nld": "Belize", "por": "Belize", "rus": "Белиз", "spa": "Belice", "svk": "Belize", "fin": "Belize", "zho": "伯利兹", "isr": "בליז"}}, "BJ": {"currency": "XOF", "callingCode": "229", "flag": "flag-bj", "name": {"common": "Benin", "cym": "Benin", "deu": "Benin", "fra": "Bénin", "hrv": "Benin", "ita": "Benin", "jpn": "ベナン", "nld": "Benin", "por": "Benin", "rus": "Бенин", "spa": "Benín", "svk": "Benin", "fin": "Benin", "zho": "贝宁", "isr": "בנין"}}, "BM": {"currency": "BMD", "callingCode": "1441", "flag": "flag-bm", "name": {"common": "Bermuda", "cym": "Bermiwda", "deu": "Bermuda", "fra": "Bermudes", "hrv": "Bermudi", "ita": "Bermuda", "jpn": "バミューダ", "nld": "Bermuda", "por": "Bermudas", "rus": "Бермудские Острова", "spa": "Bermudas", "svk": "Bermudy", "fin": "Bermuda", "zho": "百慕大", "isr": "ברמודה"}}, "BT": {"currency": "BTN", "callingCode": "975", "flag": "flag-bt", "name": {"common": "Bhutan", "cym": "Bhwtan", "deu": "Bhutan", "fra": "Bhoutan", "hrv": "Butan", "ita": "Bhutan", "jpn": "ブータン", "nld": "Bhutan", "por": "Butão", "rus": "Бутан", "spa": "Bután", "svk": "Bhután", "fin": "Bhutan", "zho": "不丹", "isr": "בהוטן"}}, "BO": {"currency": "BOB", "callingCode": "591", "flag": "flag-bo", "name": {"common": "Bolivia", "cym": "Bolifia", "deu": "Bolivien", "fra": "Bolivie", "hrv": "Bolivija", "ita": "Bolivia", "jpn": "ボリビア多民族国", "nld": "Bolivia", "por": "Bolívia", "rus": "Боливия", "spa": "Bolivia", "svk": "Bolívija", "fin": "Bolivia", "zho": "玻利维亚", "isr": "בוליביה"}}, "BA": {"currency": "BAM", "callingCode": "387", "flag": "flag-ba", "name": {"common": "Bosnia and Herzegovina", "cym": "Bosnia a Hercegovina", "deu": "Bosnien und Herzegowina", "fra": "Bosnie-Herzégovine", "hrv": "Bosna i Hercegovina", "ita": "Bosnia ed Erzegovina", "jpn": "ボスニア・ヘルツェゴビナ", "nld": "Bosnië en Herzegovina", "por": "Bósnia e Herzegovina", "rus": "Босния и Герцеговина", "spa": "Bosnia y Herzegovina", "svk": "Bosna a Hercegovina", "fin": "Bosnia ja Hertsegovina", "zho": "波斯尼亚和黑塞哥维那", "isr": "בוסניה והרצגובינה"}}, "BW": {"currency": "BWP", "callingCode": "267", "flag": "flag-bw", "name": {"common": "Botswana", "deu": "Botswana", "fra": "Botswana", "hrv": "Bocvana", "ita": "Botswana", "jpn": "ボツワナ", "nld": "Botswana", "por": "Botswana", "rus": "Ботсвана", "spa": "Botswana", "svk": "Botswana", "fin": "Botswana", "zho": "博茨瓦纳", "isr": "בוצוואנה"}}, "BV": {"currency": "NOK", "flag": "flag-bv", "name": {"common": "Bouvet Island", "deu": "Bouvetinsel", "fra": "Île Bouvet", "hrv": "Otok Bouvet", "ita": "Isola Bouvet", "jpn": "ブーベ島", "nld": "Bouveteiland", "por": "Ilha Bouvet", "rus": "Остров Буве", "spa": "Isla Bouvet", "svk": "Bouvetov ostrov", "fin": "Bouvet'nsaari", "zho": "布维岛", "isr": "איי בובה"}}, "BR": {"currency": "BRL", "callingCode": "55", "flag": "flag-br", "name": {"common": "Brazil", "cym": "Brasil", "deu": "Brasilien", "fra": "Brésil", "hrv": "Brazil", "ita": "Brasile", "jpn": "ブラジル", "nld": "Brazilië", "por": "Brasil", "rus": "Бразилия", "spa": "Brasil", "svk": "Brazília", "fin": "Brasilia", "zho": "巴西", "isr": "ברזיל"}}, "IO": {"currency": "USD", "callingCode": "246", "flag": "flag-io", "name": {"common": "British Indian Ocean Territory", "cym": "Tiriogaeth Brydeinig Cefnfor India", "deu": "Britisches Territorium im Indischen Ozean", "fra": "Territoire britannique de l'océan Indien", "hrv": "Britanski Indijskooceanski teritorij", "ita": "Territorio britannico dell'oceano indiano", "jpn": "イギリス領インド洋地域", "nld": "Britse Gebieden in de Indische Oceaan", "por": "Território Britânico do Oceano Índico", "rus": "Британская территория в Индийском океане", "spa": "Territorio Británico del Océano Índico", "svk": "Britské indickooceánske územie", "fin": "Brittiläinen Intian valtameren alue", "zho": "英属印度洋领地", "isr": "הטריטוריה הבריטית באוקיינוס ההודי"}}, "VG": {"currency": "USD", "callingCode": "1284", "flag": "flag-vg", "name": {"common": "British Virgin Islands", "deu": "Britische Jungferninseln", "fra": "Îles Vierges britanniques", "hrv": "Britanski Djevičanski Otoci", "ita": "Isole Vergini Britanniche", "jpn": "イギリス領ヴァージン諸島", "nld": "Britse Maagdeneilanden", "por": "Ilhas Virgens", "rus": "Британские Виргинские острова", "spa": "Islas Vírgenes del Reino Unido", "svk": "Panenské ostrovy", "fin": "Neitsytsaaret", "zho": "英属维尔京群岛", "isr": "איי הבתולה הבריטיים"}}, "BN": {"currency": "BND", "callingCode": "673", "flag": "flag-bn", "name": {"common": "Brunei", "cym": "Brunei", "deu": "Brunei", "fra": "Brunei", "hrv": "Brunej", "ita": "Brunei", "jpn": "ブルネイ・ダルサラーム", "nld": "Brunei", "por": "Brunei", "rus": "Бруней", "spa": "Brunei", "svk": "Brunej", "fin": "Brunei", "zho": "文莱", "isr": "ברוניי"}}, "BG": {"currency": "BGN", "callingCode": "359", "flag": "flag-bg", "name": {"common": "Bulgaria", "cym": "Bwlgaria", "deu": "Bulgarien", "fra": "Bulgarie", "hrv": "Bugarska", "ita": "Bulgaria", "jpn": "ブルガリア", "nld": "Bulgarije", "por": "Bulgária", "rus": "Болгария", "spa": "Bulgaria", "svk": "Bulharsko", "fin": "Bulgaria", "zho": "保加利亚", "isr": "בולגריה"}}, "BF": {"currency": "XOF", "callingCode": "226", "flag": "flag-bf", "name": {"common": "Burkina Faso", "cym": "Burkina Faso", "deu": "Burkina Faso", "fra": "Burkina Faso", "hrv": "Burkina Faso", "ita": "Burkina Faso", "jpn": "ブルキナファソ", "nld": "Burkina Faso", "por": "Burkina Faso", "rus": "Буркина-Фасо", "spa": "Burkina Faso", "svk": "Burkina Faso", "fin": "Burkina Faso", "zho": "布基纳法索", "isr": "בורקינה פאסו"}}, "BI": {"currency": "BIF", "callingCode": "257", "flag": "flag-bi", "name": {"common": "Burundi", "cym": "Bwrwndi", "deu": "Burundi", "fra": "Burundi", "hrv": "Burundi", "ita": "Burundi", "jpn": "ブルンジ", "nld": "Burundi", "por": "Burundi", "rus": "Бурунди", "spa": "Burundi", "svk": "Burundi", "fin": "Burundi", "zho": "布隆迪", "isr": "בורונדי"}}, "KH": {"currency": "KHR", "callingCode": "855", "flag": "flag-kh", "name": {"common": "Cambodia", "cym": "Cambodia", "deu": "Kambodscha", "fra": "Cambodge", "hrv": "Kambodža", "ita": "Cambogia", "jpn": "カンボジア", "nld": "Cambodja", "por": "Camboja", "rus": "Камбоджа", "spa": "Camboya", "svk": "Kambodža", "fin": "Kambodža", "zho": "柬埔寨", "isr": "קמבודיה"}}, "CM": {"currency": "XAF", "callingCode": "237", "flag": "flag-cm", "name": {"common": "Cameroon", "cym": "Camerŵn", "deu": "Kamerun", "fra": "Cameroun", "hrv": "Kamerun", "ita": "Camerun", "jpn": "カメルーン", "nld": "Kameroen", "por": "Camarões", "rus": "Камерун", "spa": "Camerún", "svk": "Kamerun", "fin": "Kamerun", "zho": "喀麦隆", "isr": "קמרון"}}, "CA": {"currency": "CAD", "callingCode": "1", "flag": "flag-ca", "name": {"common": "Canada", "cym": "Canada", "deu": "Kanada", "fra": "Canada", "hrv": "Kanada", "ita": "Canada", "jpn": "カナダ", "nld": "Canada", "por": "Canadá", "rus": "Канада", "spa": "Canadá", "svk": "Kanada", "fin": "Kanada", "zho": "加拿大", "isr": "קנדה"}}, "CV": {"currency": "CVE", "callingCode": "238", "flag": "flag-cv", "name": {"common": "Cape Verde", "cym": "Cape Verde", "deu": "Kap Verde", "fra": "Îles du Cap-Vert", "hrv": "Zelenortska Republika", "ita": "Capo Verde", "jpn": "カーボベルデ", "nld": "Kaapverdië", "por": "Cabo Verde", "rus": "Кабо-Верде", "spa": "Cabo Verde", "svk": "Kapverdy", "fin": "Kap Verde", "zho": "佛得角", "isr": "כף ורדה"}}, "KY": {"currency": "KYD", "callingCode": "1345", "flag": "flag-ky", "name": {"common": "Cayman Islands", "cym": "Ynysoedd_Cayman", "deu": "Kaimaninseln", "fra": "Îles Caïmans", "hrv": "Kajmanski otoci", "ita": "Isole Cayman", "jpn": "ケイマン諸島", "nld": "Caymaneilanden", "por": "Ilhas Caimão", "rus": "Каймановы острова", "spa": "Islas Caimán", "svk": "Kajmanie ostrovy", "fin": "Caymansaaret", "zho": "开曼群岛", "isr": "איי קיימן"}}, "CF": {"currency": "XAF", "callingCode": "236", "flag": "flag-cf", "name": {"common": "Central African Republic", "cym": "Gweriniaeth Canolbarth Affrica", "deu": "Zentralafrikanische Republik", "fra": "République centrafricaine", "hrv": "Srednjoafrička Republika", "ita": "Repubblica Centrafricana", "jpn": "中央アフリカ共和国", "nld": "Centraal-Afrikaanse Republiek", "por": "República Centro-Africana", "rus": "Центральноафриканская Республика", "spa": "República Centroafricana", "svk": "Stredoafrická republika", "fin": "Keski-Afrikan tasavalta", "zho": "中非共和国", "isr": "הרפובליקה של מרכז אפריקה"}}, "TD": {"currency": "XAF", "callingCode": "235", "flag": "flag-td", "name": {"common": "Chad", "cym": "Tsiad", "deu": "Tschad", "fra": "Tchad", "hrv": "Čad", "ita": "Ciad", "jpn": "チャド", "nld": "Tsjaad", "por": "Chade", "rus": "Чад", "spa": "Chad", "svk": "Čad", "fin": "Tšad", "zho": "乍得", "isr": "צ׳אד"}}, "CL": {"currency": "CLF", "callingCode": "56", "flag": "flag-cl", "name": {"common": "Chile", "cym": "Chile", "deu": "Chile", "fra": "Chili", "hrv": "Čile", "ita": "Cile", "jpn": "チリ", "nld": "Chili", "por": "Chile", "rus": "Чили", "spa": "Chile", "svk": "Čile", "fin": "Chile", "zho": "智利", "isr": "צ׳ילה"}}, "CN": {"currency": "CNY", "callingCode": "86", "flag": "flag-cn", "name": {"common": "China", "cym": "Tsieina", "deu": "China", "fra": "Chine", "hrv": "Kina", "ita": "Cina", "jpn": "中国", "nld": "China", "por": "China", "rus": "Китай", "spa": "China", "svk": "Čína", "fin": "Kiina", "isr": "סין"}}, "CX": {"currency": "AUD", "callingCode": "61", "flag": "flag-cx", "name": {"common": "Christmas Island", "cym": "Ynys y Nadolig", "deu": "Weihnachtsinsel", "fra": "Île Christmas", "hrv": "Božićni otok", "ita": "Isola di Natale", "jpn": "クリスマス島", "nld": "Christmaseiland", "por": "Ilha do Natal", "rus": "Остров Рождества", "spa": "Isla de Navidad", "svk": "Vianočnú ostrov", "fin": "Joulusaari", "zho": "圣诞岛", "isr": "האי כריסטמס"}}, "CC": {"currency": "AUD", "callingCode": "61", "flag": "flag-cc", "name": {"common": "Cocos (Keeling) Islands", "cym": "Ynysoedd Cocos", "deu": "Kokosinseln", "fra": "Îles Cocos", "hrv": "Kokosovi Otoci", "ita": "Isole Cocos e Keeling", "jpn": "ココス(キーリング)諸島", "nld": "Cocoseilanden", "por": "Ilhas Cocos (Keeling)", "rus": "Кокосовые острова", "spa": "Islas Cocos o Islas Keeling", "svk": "Kokosové ostrovy", "fin": "Kookossaaret", "zho": "科科斯", "isr": "איי קוקוס (קילינג)"}}, "CO": {"currency": "COP", "callingCode": "57", "flag": "flag-co", "name": {"common": "Colombia", "cym": "Colombia", "deu": "Kolumbien", "fra": "Colombie", "hrv": "Kolumbija", "ita": "Colombia", "jpn": "コロンビア", "nld": "Colombia", "por": "Colômbia", "rus": "Колумбия", "spa": "Colombia", "svk": "Kolumbia", "fin": "Kolumbia", "zho": "哥伦比亚", "isr": "קולומביה"}}, "KM": {"currency": "KMF", "callingCode": "269", "flag": "flag-km", "name": {"common": "Comoros", "cym": "Comoros", "deu": "Union der Komoren", "fra": "Comores", "hrv": "Komori", "ita": "Comore", "jpn": "コモロ", "nld": "Comoren", "por": "Comores", "rus": "Коморы", "spa": "Comoras", "svk": "Komory", "fin": "Komorit", "zho": "科摩罗", "isr": "קומורו"}}, "CK": {"currency": "NZD", "callingCode": "682", "flag": "flag-ck", "name": {"common": "Cook Islands", "cym": "Ynysoedd Cook", "deu": "Cookinseln", "fra": "Îles Cook", "hrv": "Cookovo Otočje", "ita": "Isole Cook", "jpn": "クック諸島", "nld": "Cookeilanden", "por": "Ilhas Cook", "rus": "Острова Кука", "spa": "Islas Cook", "svk": "Cookove ostrovy", "fin": "Cookinsaaret", "zho": "库克群岛", "isr": "איי קוק"}}, "CR": {"currency": "CRC", "callingCode": "506", "flag": "flag-cr", "name": {"common": "Costa Rica", "cym": "Costa Rica", "deu": "Costa Rica", "fra": "Costa Rica", "hrv": "Kostarika", "ita": "Costa Rica", "jpn": "コスタリカ", "nld": "Costa Rica", "por": "Costa Rica", "rus": "Коста-Рика", "spa": "Costa Rica", "svk": "Kostarika", "fin": "Costa Rica", "zho": "哥斯达黎加", "isr": "קוסטה ריקה"}}, "HR": {"currency": "HRK", "callingCode": "385", "flag": "flag-hr", "name": {"common": "Croatia", "cym": "Croatia", "deu": "Kroatien", "fra": "Croatie", "hrv": "Hrvatska", "ita": "Croazia", "jpn": "クロアチア", "nld": "Kroatië", "por": "Croácia", "rus": "Хорватия", "spa": "Croacia", "svk": "Chorvátsko", "fin": "Kroatia", "zho": "克罗地亚", "isr": "קרואטיה"}}, "CU": {"currency": "CUC", "callingCode": "53", "flag": "flag-cu", "name": {"common": "Cuba", "cym": "Ciwba", "deu": "Kuba", "fra": "Cuba", "hrv": "Kuba", "ita": "Cuba", "jpn": "キューバ", "nld": "Cuba", "por": "Cuba", "rus": "Куба", "spa": "Cuba", "svk": "Kuba", "fin": "Kuuba", "zho": "古巴", "isr": "קובה"}}, "CW": {"currency": "ANG", "callingCode": "5999", "flag": "flag-cw", "name": {"common": "Curaçao", "deu": "Curaçao", "fra": "Curaçao", "nld": "Curaçao", "por": "ilha da Curação", "rus": "Кюрасао", "spa": "Curazao", "svk": "CuraÇao", "fin": "Curaçao", "zho": "库拉索", "isr": "קוראסאו"}}, "CY": {"currency": "EUR", "callingCode": "357", "flag": "flag-cy", "name": {"common": "Cyprus", "cym": "Cyprus", "deu": "Zypern", "fra": "Chypre", "hrv": "Cipar", "ita": "Cipro", "jpn": "キプロス", "nld": "Cyprus", "por": "Chipre", "rus": "Кипр", "spa": "Chipre", "svk": "Cyprus", "fin": "Kypros", "zho": "塞浦路斯", "isr": "קפריסין"}}, "CZ": {"currency": "CZK", "callingCode": "420", "flag": "flag-cz", "name": {"common": "Czech Republic", "cym": "Y Weriniaeth Tsiec", "deu": "Tschechische Republik", "fra": "République tchèque", "hrv": "Češka", "ita": "Repubblica Ceca", "jpn": "チェコ", "nld": "Tsjechië", "por": "República Checa", "rus": "Чехия", "spa": "República Checa", "svk": "Česko", "fin": "Tšekki", "zho": "捷克", "isr": "צ׳כיה"}}, "CD": {"currency": "CDF", "callingCode": "243", "flag": "flag-cd", "name": {"common": "DR Congo", "cym": "Gweriniaeth Ddemocrataidd Congo", "deu": "Kongo (Dem. Rep.)", "fra": "Congo (Rép. dém.)", "hrv": "Kongo, Demokratska Republika", "ita": "Congo (Rep. Dem.)", "jpn": "コンゴ民主共和国", "nld": "Congo (DRC)", "por": "República Democrática do Congo", "rus": "Демократическая Республика Конго", "spa": "Congo (Rep. Dem.)", "svk": "Kongo", "fin": "Kongon demokraattinen tasavalta", "zho": "民主刚果", "isr": "קונגו - קינשאסה"}}, "DK": {"currency": "DKK", "callingCode": "45", "flag": "flag-dk", "name": {"common": "Denmark", "cym": "Denmarc", "deu": "Dänemark", "fra": "Danemark", "hrv": "Danska", "ita": "Danimarca", "jpn": "デンマーク", "nld": "Denemarken", "por": "Dinamarca", "rus": "Дания", "spa": "Dinamarca", "svk": "Dánsko", "fin": "Tanska", "zho": "丹麦", "isr": "דנמרק"}}, "DJ": {"currency": "DJF", "callingCode": "253", "flag": "flag-dj", "name": {"common": "Djibouti", "cym": "Djibouti", "deu": "Dschibuti", "fra": "Djibouti", "hrv": "Džibuti", "ita": "Gibuti", "jpn": "ジブチ", "nld": "Djibouti", "por": "Djibouti", "rus": "Джибути", "spa": "Djibouti", "svk": "Džibutsko", "fin": "Dijibouti", "zho": "吉布提", "isr": "ג׳יבוטי"}}, "DM": {"currency": "XCD", "callingCode": "1767", "flag": "flag-dm", "name": {"common": "Dominica", "cym": "Dominica", "deu": "Dominica", "fra": "Dominique", "hrv": "Dominika", "ita": "Dominica", "jpn": "ドミニカ国", "nld": "Dominica", "por": "Dominica", "rus": "Доминика", "spa": "Dominica", "svk": "Dominika", "fin": "Dominica", "zho": "多米尼加", "isr": "דומיניקה"}}, "DO": {"currency": "DOP", "callingCode": "1809", "flag": "flag-do", "name": {"common": "Dominican Republic", "cym": "Gweriniaeth_Dominica", "deu": "Dominikanische Republik", "fra": "République dominicaine", "hrv": "Dominikanska Republika", "ita": "Repubblica Dominicana", "jpn": "ドミニカ共和国", "nld": "Dominicaanse Republiek", "por": "República Dominicana", "rus": "Доминиканская Республика", "spa": "República Dominicana", "svk": "Dominikánska republika", "fin": "Dominikaaninen tasavalta", "zho": "多明尼加", "isr": "הרפובליקה הדומיניקנית"}}, "EC": {"currency": "USD", "callingCode": "593", "flag": "flag-ec", "name": {"common": "Ecuador", "cym": "Ecwador", "deu": "Ecuador", "fra": "Équateur", "hrv": "Ekvador", "ita": "Ecuador", "jpn": "エクアドル", "nld": "Ecuador", "por": "Equador", "rus": "Эквадор", "spa": "Ecuador", "svk": "Ekvádor", "fin": "Ecuador", "zho": "厄瓜多尔", "isr": "אקוודור"}}, "EG": {"currency": "EGP", "callingCode": "20", "flag": "flag-eg", "name": {"common": "Egypt", "cym": "Yr Aifft", "deu": "Ägypten", "fra": "Égypte", "hrv": "Egipat", "ita": "Egitto", "jpn": "エジプト", "nld": "Egypte", "por": "Egito", "rus": "Египет", "spa": "Egipto", "svk": "Egypt", "fin": "Egypti", "zho": "埃及", "isr": "מצרים"}}, "SV": {"currency": "SVC", "callingCode": "503", "flag": "flag-sv", "name": {"common": "El Salvador", "cym": "El Salvador", "deu": "El Salvador", "fra": "Salvador", "hrv": "Salvador", "ita": "El Salvador", "jpn": "エルサルバドル", "nld": "El Salvador", "por": "El Salvador", "rus": "Сальвадор", "spa": "El Salvador", "svk": "Salvádor", "fin": "El Salvador", "zho": "萨尔瓦多", "isr": "אל סלבדור"}}, "GQ": {"currency": "XAF", "callingCode": "240", "flag": "flag-gq", "name": {"common": "Equatorial Guinea", "cym": "Gini Gyhydeddol", "deu": "Äquatorialguinea", "fra": "Guinée équatoriale", "hrv": "Ekvatorijalna Gvineja", "ita": "Guinea Equatoriale", "jpn": "赤道ギニア", "nld": "Equatoriaal-Guinea", "por": "Guiné Equatorial", "rus": "Экваториальная Гвинея", "spa": "Guinea Ecuatorial", "svk": "Rovníková Guinea", "fin": "Päiväntasaajan Guinea", "zho": "赤道几内亚", "isr": "גינאה המשוונית"}}, "ER": {"currency": "ERN", "callingCode": "291", "flag": "flag-er", "name": {"common": "Eritrea", "cym": "Eritrea", "deu": "Eritrea", "fra": "Érythrée", "hrv": "Eritreja", "ita": "Eritrea", "jpn": "エリトリア", "nld": "Eritrea", "por": "Eritreia", "rus": "Эритрея", "spa": "Eritrea", "svk": "Eritrea", "fin": "Eritrea", "zho": "厄立特里亚", "isr": "אריתריאה"}}, "EE": {"currency": "EUR", "callingCode": "372", "flag": "flag-ee", "name": {"common": "Estonia", "cym": "Estonia", "deu": "Estland", "fra": "Estonie", "hrv": "Estonija", "ita": "Estonia", "jpn": "エストニア", "nld": "Estland", "por": "Estónia", "rus": "Эстония", "spa": "Estonia", "svk": "Estónsko", "fin": "Viro", "zho": "爱沙尼亚", "isr": "אסטוניה"}}, "ET": {"currency": "ETB", "callingCode": "251", "flag": "flag-et", "name": {"common": "Ethiopia", "cym": "Ethiopia", "deu": "Äthiopien", "fra": "Éthiopie", "hrv": "Etiopija", "ita": "Etiopia", "jpn": "エチオピア", "nld": "Ethiopië", "por": "Etiópia", "rus": "Эфиопия", "spa": "Etiopía", "svk": "Etiópia", "fin": "Etiopia", "zho": "埃塞俄比亚", "isr": "אתיופיה"}}, "FK": {"currency": "FKP", "callingCode": "500", "flag": "flag-fk", "name": {"common": "Falkland Islands", "deu": "Falklandinseln", "fra": "Îles Malouines", "hrv": "Falklandski Otoci", "ita": "Isole Falkland o Isole Malvine", "jpn": "フォークランド(マルビナス)諸島", "nld": "Falklandeilanden", "por": "Ilhas Malvinas", "rus": "Фолклендские острова", "spa": "Islas Malvinas", "svk": "Falklandy", "fin": "Falkandinsaaret", "zho": "福克兰群岛", "isr": "איי פוקלנד"}}, "FO": {"currency": "DKK", "callingCode": "298", "flag": "flag-fo", "name": {"common": "Faroe Islands", "deu": "Färöer-Inseln", "fra": "Îles Féroé", "hrv": "Farski Otoci", "ita": "Isole Far Oer", "jpn": "フェロー諸島", "nld": "Faeröer", "por": "Ilhas Faroé", "rus": "Фарерские острова", "spa": "Islas Faroe", "svk": "Faerské ostrovy", "fin": "Färsaaret", "zho": "法罗群岛", "isr": "איי פארו"}}, "FJ": {"currency": "FJD", "callingCode": "679", "flag": "flag-fj", "name": {"common": "Fiji", "deu": "Fidschi", "fra": "Fidji", "hrv": "Fiđi", "ita": "Figi", "jpn": "フィジー", "nld": "Fiji", "por": "Fiji", "rus": "Фиджи", "spa": "Fiyi", "svk": "Fidži", "fin": "Fidži", "zho": "斐济", "isr": "פיג׳י"}}, "FI": {"currency": "EUR", "callingCode": "358", "flag": "flag-fi", "name": {"common": "Finland", "deu": "Finnland", "fra": "Finlande", "hrv": "Finska", "ita": "Finlandia", "jpn": "フィンランド", "nld": "Finland", "por": "Finlândia", "rus": "Финляндия", "spa": "Finlandia", "svk": "Fínsko", "fin": "Suomi", "zho": "芬兰", "isr": "פינלנד"}}, "FR": {"currency": "EUR", "callingCode": "33", "flag": "flag-fr", "name": {"common": "France", "deu": "Frankreich", "fra": "France", "hrv": "Francuska", "ita": "Francia", "jpn": "フランス", "nld": "Frankrijk", "por": "França", "rus": "Франция", "spa": "Francia", "svk": "Francúzsko", "fin": "Ranska", "zho": "法国", "isr": "צרפת"}}, "GF": {"currency": "EUR", "callingCode": "594", "flag": "flag-gf", "name": {"common": "French Guiana", "deu": "Französisch Guyana", "fra": "Guyane", "hrv": "Francuska Gvajana", "ita": "Guyana francese", "jpn": "フランス領ギアナ", "nld": "Frans-Guyana", "por": "Guiana Francesa", "rus": "Французская Гвиана", "spa": "Guayana Francesa", "svk": "Guyana", "fin": "Ranskan Guayana", "zho": "法属圭亚那", "isr": "גיאנה הצרפתית"}}, "PF": {"currency": "XPF", "callingCode": "689", "flag": "flag-pf", "name": {"common": "French Polynesia", "deu": "Französisch-Polynesien", "fra": "Polynésie française", "hrv": "Francuska Polinezija", "ita": "Polinesia Francese", "jpn": "フランス領ポリネシア", "nld": "Frans-Polynesië", "por": "Polinésia Francesa", "rus": "Французская Полинезия", "spa": "Polinesia Francesa", "svk": "Francúzska Polynézia", "fin": "Ranskan Polynesia", "zho": "法属波利尼西亚", "isr": "פולינזיה הצרפתית"}}, "TF": {"currency": "EUR", "flag": "flag-tf", "name": {"common": "French Southern and Antarctic Lands", "deu": "Französische Süd-und Antarktisgebiete", "fra": "Terres australes et antarctiques françaises", "hrv": "Francuski južni i antarktički teritoriji", "ita": "Territori Francesi del Sud", "jpn": "フランス領南方・南極地域", "nld": "Franse Gebieden in de zuidelijke Indische Oceaan", "por": "Terras Austrais e Antárticas Francesas", "rus": "Французские Южные и Антарктические территории", "spa": "Tierras Australes y Antárticas Francesas", "svk": "Francúzske juŽné a antarktické územia", "fin": "Ranskan eteläiset ja antarktiset alueet", "zho": "法国南部和南极土地", "isr": "הטריטוריות הדרומיות של צרפת"}}, "GA": {"currency": "XAF", "callingCode": "241", "flag": "flag-ga", "name": {"common": "Gabon", "deu": "Gabun", "fra": "Gabon", "hrv": "Gabon", "ita": "Gabon", "jpn": "ガボン", "nld": "Gabon", "por": "Gabão", "rus": "Габон", "spa": "Gabón", "svk": "Gabon", "fin": "Gabon", "zho": "加蓬", "isr": "גבון"}}, "GM": {"currency": "GMD", "callingCode": "220", "flag": "flag-gm", "name": {"common": "Gambia", "deu": "Gambia", "fra": "Gambie", "hrv": "Gambija", "ita": "Gambia", "jpn": "ガンビア", "nld": "Gambia", "por": "Gâmbia", "rus": "Гамбия", "spa": "Gambia", "svk": "Gambia", "fin": "Gambia", "zho": "冈比亚", "isr": "גמביה"}}, "GE": {"currency": "GEL", "callingCode": "995", "flag": "flag-ge", "name": {"common": "Georgia", "deu": "Georgien", "fra": "Géorgie", "hrv": "Gruzija", "ita": "Georgia", "jpn": "グルジア", "nld": "Georgië", "por": "Geórgia", "rus": "Грузия", "spa": "Georgia", "svk": "Gruzínsko", "fin": "Georgia", "zho": "格鲁吉亚", "isr": "גאורגיה"}}, "DE": {"currency": "EUR", "callingCode": "49", "flag": "flag-de", "name": {"common": "Germany", "deu": "Deutschland", "fra": "Allemagne", "hrv": "Njemačka", "ita": "Germania", "jpn": "ドイツ", "nld": "Duitsland", "por": "Alemanha", "rus": "Германия", "spa": "Alemania", "svk": "Nemecko", "fin": "Saksa", "zho": "德国", "isr": "גרמניה"}}, "GH": {"currency": "GHS", "callingCode": "233", "flag": "flag-gh", "name": {"common": "Ghana", "deu": "Ghana", "fra": "Ghana", "hrv": "Gana", "ita": "Ghana", "jpn": "ガーナ", "nld": "Ghana", "por": "Gana", "rus": "Гана", "spa": "Ghana", "svk": "Ghana", "fin": "Ghana", "zho": "加纳", "isr": "גאנה"}}, "GI": {"currency": "GIP", "callingCode": "350", "flag": "flag-gi", "name": {"common": "Gibraltar", "deu": "Gibraltar", "fra": "Gibraltar", "hrv": "Gibraltar", "ita": "Gibilterra", "jpn": "ジブラルタル", "nld": "Gibraltar", "por": "Gibraltar", "rus": "Гибралтар", "spa": "Gibraltar", "svk": "Gibraltár", "fin": "Gibraltar", "zho": "直布罗陀", "isr": "גיברלטר"}}, "GR": {"currency": "EUR", "callingCode": "30", "flag": "flag-gr", "name": {"common": "Greece", "deu": "Griechenland", "fra": "Grèce", "hrv": "Grčka", "ita": "Grecia", "jpn": "ギリシャ", "nld": "Griekenland", "por": "Grécia", "rus": "Греция", "spa": "Grecia", "svk": "Greécko", "fin": "Kreikka", "zho": "希腊", "isr": "יוון"}}, "GL": {"currency": "DKK", "callingCode": "299", "flag": "flag-gl", "name": {"common": "Greenland", "deu": "Grönland", "fra": "Groenland", "hrv": "Grenland", "ita": "Groenlandia", "jpn": "グリーンランド", "nld": "Groenland", "por": "Gronelândia", "rus": "Гренландия", "spa": "Groenlandia", "svk": "Grónsko", "fin": "Groönlanti", "zho": "格陵兰", "isr": "גרינלנד"}}, "GD": {"currency": "XCD", "callingCode": "1473", "flag": "flag-gd", "name": {"common": "Grenada", "deu": "Grenada", "fra": "Grenade", "hrv": "Grenada", "ita": "Grenada", "jpn": "グレナダ", "nld": "Grenada", "por": "Granada", "rus": "Гренада", "spa": "Grenada", "svk": "Grenada", "fin": "Grenada", "zho": "格林纳达", "isr": "גרנדה"}}, "GP": {"currency": "EUR", "callingCode": "590", "flag": "flag-gp", "name": {"common": "Guadeloupe", "deu": "Guadeloupe", "fra": "Guadeloupe", "hrv": "Gvadalupa", "ita": "Guadeloupa", "jpn": "グアドループ", "nld": "Guadeloupe", "por": "Guadalupe", "rus": "Гваделупа", "spa": "Guadalupe", "svk": "Guadeloupe", "fin": "Guadeloupe", "zho": "瓜德罗普岛", "isr": "גוואדלופ"}}, "GU": {"currency": "USD", "callingCode": "1671", "flag": "flag-gu", "name": {"common": "Guam", "deu": "Guam", "fra": "Guam", "hrv": "Guam", "ita": "Guam", "jpn": "グアム", "nld": "Guam", "por": "Guam", "rus": "Гуам", "spa": "Guam", "svk": "Guam", "fin": "Guam", "zho": "关岛", "isr": "גואם"}}, "GT": {"currency": "GTQ", "callingCode": "502", "flag": "flag-gt", "name": {"common": "Guatemala", "deu": "Guatemala", "fra": "Guatemala", "hrv": "Gvatemala", "ita": "Guatemala", "jpn": "グアテマラ", "nld": "Guatemala", "por": "Guatemala", "rus": "Гватемала", "spa": "Guatemala", "svk": "Guatemala", "fin": "Guatemala", "zho": "危地马拉", "isr": "גואטמלה"}}, "GG": {"currency": "GBP", "callingCode": "44", "flag": "flag-gg", "name": {"common": "Guernsey", "deu": "Guernsey", "fra": "Guernesey", "hrv": "Guernsey", "ita": "Guernsey", "jpn": "ガーンジー", "nld": "Guernsey", "por": "Guernsey", "rus": "Гернси", "spa": "Guernsey", "svk": "Guernsey", "fin": "Guernsey", "zho": "根西岛", "isr": "גרנסי"}}, "GN": {"currency": "GNF", "callingCode": "224", "flag": "flag-gn", "name": {"common": "Guinea", "deu": "Guinea", "fra": "Guinée", "hrv": "Gvineja", "ita": "Guinea", "jpn": "ギニア", "nld": "Guinee", "por": "Guiné", "rus": "Гвинея", "spa": "Guinea", "svk": "Guinea", "fin": "Guinea", "zho": "几内亚", "isr": "גינאה"}}, "GW": {"currency": "XOF", "callingCode": "245", "flag": "flag-gw", "name": {"common": "Guinea-Bissau", "deu": "Guinea-Bissau", "fra": "Guinée-Bissau", "hrv": "Gvineja Bisau", "ita": "Guinea-Bissau", "jpn": "ギニアビサウ", "nld": "Guinee-Bissau", "por": "Guiné-Bissau", "rus": "Гвинея-Бисау", "spa": "Guinea-Bisáu", "svk": "Guinea-Bissau", "fin": "Guinea-Bissau", "zho": "几内亚比绍", "isr": "גינאה ביסאו"}}, "GY": {"currency": "GYD", "callingCode": "592", "flag": "flag-gy", "name": {"common": "Guyana", "deu": "Guyana", "fra": "Guyana", "hrv": "Gvajana", "ita": "Guyana", "jpn": "ガイアナ", "nld": "Guyana", "por": "Guiana", "rus": "Гайана", "spa": "Guyana", "svk": "Guyana", "fin": "Guayana", "zho": "圭亚那", "isr": "גיאנה"}}, "HT": {"currency": "HTG", "callingCode": "509", "flag": "flag-ht", "name": {"common": "Haiti", "deu": "Haiti", "fra": "Haïti", "hrv": "Haiti", "ita": "Haiti", "jpn": "ハイチ", "nld": "Haïti", "por": "Haiti", "rus": "Гаити", "spa": "Haiti", "svk": "Haiti", "fin": "Haiti", "zho": "海地", "isr": "האיטי"}}, "HM": {"currency": "AUD", "flag": "flag-hm", "name": {"common": "Heard Island and McDonald Islands", "deu": "Heard und die McDonaldinseln", "fra": "Îles Heard-et-MacDonald", "hrv": "Otok Heard i otočje McDonald", "ita": "Isole Heard e McDonald", "jpn": "ハード島とマクドナルド諸島", "nld": "Heard-en McDonaldeilanden", "por": "Ilha Heard e Ilhas McDonald", "rus": "Остров Херд и острова Макдональд", "spa": "Islas Heard y McDonald", "svk": "Heardov ostrov", "fin": "Heard ja McDonaldinsaaret", "zho": "赫德岛和麦当劳群岛", "isr": "איי הרד ומקדונלד"}}, "HN": {"currency": "HNL", "callingCode": "504", "flag": "flag-hn", "name": {"common": "Honduras", "deu": "Honduras", "fra": "Honduras", "hrv": "Honduras", "ita": "Honduras", "jpn": "ホンジュラス", "nld": "Honduras", "por": "Honduras", "rus": "Гондурас", "spa": "Honduras", "fin": "Honduras", "zho": "洪都拉斯", "isr": "הונדורס"}}, "HK": {"currency": "HKD", "callingCode": "852", "flag": "flag-hk", "name": {"common": "Hong Kong", "deu": "Hongkong", "fra": "Hong Kong", "hrv": "Hong Kong", "ita": "Hong Kong", "jpn": "香港", "nld": "Hongkong", "por": "Hong Kong", "rus": "Гонконг", "spa": "Hong Kong", "svk": "Hongkong", "fin": "Hongkong", "isr": "הונג קונג (מחוז מנהלי מיוחד של סין)"}}, "HU": {"currency": "HUF", "callingCode": "36", "flag": "flag-hu", "name": {"common": "Hungary", "deu": "Ungarn", "fra": "Hongrie", "hrv": "Mađarska", "ita": "Ungheria", "jpn": "ハンガリー", "nld": "Hongarije", "por": "Hungria", "rus": "Венгрия", "spa": "Hungría", "svk": "Maďarsko", "fin": "Unkari", "zho": "匈牙利", "isr": "הונגריה"}}, "IS": {"currency": "ISK", "callingCode": "354", "flag": "flag-is", "name": {"common": "Iceland", "deu": "Island", "fra": "Islande", "hrv": "Island", "ita": "Islanda", "jpn": "アイスランド", "nld": "IJsland", "por": "Islândia", "rus": "Исландия", "spa": "Islandia", "svk": "Island", "fin": "Islanti", "zho": "冰岛", "isr": "איסלנד"}}, "IN": {"currency": "INR", "callingCode": "91", "flag": "flag-in", "name": {"common": "India", "deu": "Indien", "fra": "Inde", "hrv": "Indija", "ita": "India", "jpn": "インド", "nld": "India", "por": "Índia", "rus": "Индия", "spa": "India", "svk": "India", "fin": "Intia", "zho": "印度", "isr": "הודו"}}, "ID": {"currency": "IDR", "callingCode": "62", "flag": "flag-id", "name": {"common": "Indonesia", "deu": "Indonesien", "fra": "Indonésie", "hrv": "Indonezija", "ita": "Indonesia", "jpn": "インドネシア", "nld": "Indonesië", "por": "Indonésia", "rus": "Индонезия", "spa": "Indonesia", "svk": "Indonézia", "fin": "Indonesia", "zho": "印度尼西亚", "isr": "אינדונזיה"}}, "IR": {"currency": "IRR", "callingCode": "98", "flag": "flag-ir", "name": {"common": "Iran", "deu": "Iran", "fra": "Iran", "hrv": "Iran", "ita": "Iran", "jpn": "イラン・イスラム共和国", "nld": "Iran", "por": "Irão", "rus": "Иран", "spa": "Iran", "svk": "Irán", "fin": "Iran", "zho": "伊朗", "isr": "איראן"}}, "IQ": {"currency": "IQD", "callingCode": "964", "flag": "flag-iq", "name": {"common": "Iraq", "deu": "Irak", "fra": "Irak", "hrv": "Irak", "ita": "Iraq", "jpn": "イラク", "nld": "Irak", "por": "Iraque", "rus": "Ирак", "spa": "Irak", "svk": "Irak", "fin": "Irak", "zho": "伊拉克", "isr": "עיראק"}}, "IE": {"currency": "EUR", "callingCode": "353", "flag": "flag-ie", "name": {"common": "Ireland", "deu": "Irland", "fra": "Irlande", "hrv": "Irska", "ita": "Irlanda", "jpn": "アイルランド", "nld": "Ierland", "por": "Irlanda", "rus": "Ирландия", "spa": "Irlanda", "svk": "Írsko", "fin": "Irlanti", "zho": "爱尔兰", "isr": "אירלנד"}}, "IM": {"currency": "GBP", "callingCode": "44", "flag": "flag-im", "name": {"common": "Isle of Man", "deu": "Insel Man", "fra": "Île de Man", "hrv": "Otok Man", "ita": "Isola di Man", "jpn": "マン島", "nld": "Isle of Man", "por": "Ilha de Man", "rus": "Остров Мэн", "spa": "Isla de Man", "svk": "Man", "fin": "Mansaari", "zho": "马恩岛", "isr": "האי מאן"}}, "IL": {"currency": "ILS", "callingCode": "972", "flag": "flag-il", "name": {"common": "Israel", "deu": "Israel", "fra": "Israël", "hrv": "Izrael", "ita": "Israele", "jpn": "イスラエル", "nld": "Israël", "por": "Israel", "rus": "Израиль", "spa": "Israel", "svk": "Izrael", "fin": "Israel", "zho": "以色列", "isr": "ישראל"}}, "IT": {"currency": "EUR", "callingCode": "39", "flag": "flag-it", "name": {"common": "Italy", "deu": "Italien", "fra": "Italie", "hrv": "Italija", "ita": "Italia", "jpn": "イタリア", "nld": "Italië", "por": "Itália", "rus": "Италия", "spa": "Italia", "svk": "Taliansko", "fin": "Italia", "zho": "意大利", "isr": "איטליה"}}, "CI": {"currency": "XOF", "callingCode": "225", "flag": "flag-ci", "name": {"common": "Ivory Coast", "deu": "Elfenbeinküste", "fra": "Côte d'Ivoire", "hrv": "Obala Bjelokosti", "ita": "Costa d'Avorio", "jpn": "コートジボワール", "nld": "Ivoorkust", "por": "Costa do Marfim", "rus": "Кот-д’Ивуар", "spa": "Costa de Marfil", "svk": "Pobržie Slonoviny", "fin": "Norsunluurannikko", "zho": "科特迪瓦", "isr": "חוף השנהב"}}, "JM": {"currency": "JMD", "callingCode": "1876", "flag": "flag-jm", "name": {"common": "Jamaica", "deu": "Jamaika", "fra": "Jamaïque", "hrv": "Jamajka", "ita": "Giamaica", "jpn": "ジャマイカ", "nld": "Jamaica", "por": "Jamaica", "rus": "Ямайка", "spa": "Jamaica", "svk": "Jamajka", "fin": "Jamaika", "zho": "牙买加", "isr": "ג׳מייקה"}}, "JP": {"currency": "JPY", "callingCode": "81", "flag": "flag-jp", "name": {"common": "Japan", "deu": "Japan", "fra": "Japon", "hrv": "Japan", "ita": "Giappone", "jpn": "日本", "nld": "Japan", "por": "Japão", "rus": "Япония", "spa": "Japón", "svk": "Japonsko", "fin": "Japani", "zho": "日本", "isr": "יפן"}}, "JE": {"currency": "GBP", "callingCode": "44", "flag": "flag-je", "name": {"common": "Jersey", "deu": "Jersey", "fra": "Jersey", "hrv": "Jersey", "ita": "Isola di Jersey", "jpn": "ジャージー", "nld": "Jersey", "por": "Jersey", "rus": "Джерси", "spa": "Jersey", "svk": "Jersey", "fin": "Jersey", "zho": "泽西岛", "isr": "ג׳רסי"}}, "JO": {"currency": "JOD", "callingCode": "962", "flag": "flag-jo", "name": {"common": "Jordan", "deu": "Jordanien", "fra": "Jordanie", "hrv": "Jordan", "ita": "Giordania", "jpn": "ヨルダン", "nld": "Jordanië", "por": "Jordânia", "rus": "Иордания", "spa": "Jordania", "svk": "Jordánsko", "fin": "Jordania", "zho": "约旦", "isr": "ירדן"}}, "KZ": {"currency": "KZT", "callingCode": "76", "flag": "flag-kz", "name": {"common": "Kazakhstan", "deu": "Kasachstan", "fra": "Kazakhstan", "hrv": "Kazahstan", "ita": "Kazakistan", "jpn": "カザフスタン", "nld": "Kazachstan", "por": "Cazaquistão", "rus": "Казахстан", "spa": "Kazajistán", "svk": "Kazachstan", "fin": "Kazakstan", "zho": "哈萨克斯坦", "isr": "קזחסטן"}}, "KE": {"currency": "KES", "callingCode": "254", "flag": "flag-ke", "name": {"common": "Kenya", "deu": "Kenia", "fra": "Kenya", "hrv": "Kenija", "ita": "Kenya", "jpn": "ケニア", "nld": "Kenia", "por": "Quénia", "rus": "Кения", "spa": "Kenia", "svk": "Keňa", "fin": "Kenia", "zho": "肯尼亚", "isr": "קניה"}}, "KI": {"currency": "AUD", "callingCode": "686", "flag": "flag-ki", "name": {"common": "Kiribati", "deu": "Kiribati", "fra": "Kiribati", "hrv": "Kiribati", "ita": "Kiribati", "jpn": "キリバス", "nld": "Kiribati", "por": "Kiribati", "rus": "Кирибати", "spa": "Kiribati", "svk": "Kiribati", "fin": "Kiribati", "zho": "基里巴斯", "isr": "קיריבאטי"}}, "XK": {"currency": "EUR", "callingCode": "383", "flag": "flag-xk", "name": {"common": "Kosovo", "deu": "Kosovo", "fra": "Kosovo", "hrv": "Kosovo", "ita": "Kosovo", "nld": "Kosovo", "por": "Kosovo", "rus": "Республика Косово", "spa": "Kosovo", "svk": "Kosovo", "fin": "Kosovo", "zho": "科索沃", "isr": "קוסובו"}}, "KW": {"currency": "KWD", "callingCode": "965", "flag": "flag-kw", "name": {"common": "Kuwait", "deu": "Kuwait", "fra": "Koweït", "hrv": "Kuvajt", "ita": "Kuwait", "jpn": "クウェート", "nld": "Koeweit", "por": "Kuwait", "rus": "Кувейт", "spa": "Kuwait", "svk": "Kuvajt", "fin": "Kuwait", "zho": "科威特", "isr": "כווית"}}, "KG": {"currency": "KGS", "callingCode": "996", "flag": "flag-kg", "name": {"common": "Kyrgyzstan", "deu": "Kirgisistan", "fra": "Kirghizistan", "hrv": "Kirgistan", "ita": "Kirghizistan", "jpn": "キルギス", "nld": "Kirgizië", "por": "Quirguistão", "rus": "Киргизия", "spa": "Kirguizistán", "svk": "Kirgizsko", "fin": "Kirgisia", "zho": "吉尔吉斯斯坦", "isr": "קירגיזסטן"}}, "LA": {"currency": "LAK", "callingCode": "856", "flag": "flag-la", "name": {"common": "Laos", "deu": "Laos", "fra": "Laos", "hrv": "Laos", "ita": "Laos", "jpn": "ラオス人民民主共和国", "nld": "Laos", "por": "Laos", "rus": "Лаос", "spa": "Laos", "svk": "Laos", "fin": "Laos", "zho": "老挝", "isr": "לאוס"}}, "LV": {"currency": "EUR", "callingCode": "371", "flag": "flag-lv", "name": {"common": "Latvia", "deu": "Lettland", "fra": "Lettonie", "hrv": "Latvija", "ita": "Lettonia", "jpn": "ラトビア", "nld": "Letland", "por": "Letónia", "rus": "Латвия", "spa": "Letonia", "svk": "Lotyšsko", "fin": "Latvia", "zho": "拉脱维亚", "isr": "לטביה"}}, "LB": {"currency": "LBP", "callingCode": "961", "flag": "flag-lb", "name": {"common": "Lebanon", "deu": "Libanon", "fra": "Liban", "hrv": "Libanon", "ita": "Libano", "jpn": "レバノン", "nld": "Libanon", "por": "Líbano", "rus": "Ливан", "spa": "Líbano", "svk": "Libanon", "fin": "Libanon", "zho": "黎巴嫩", "isr": "לבנון"}}, "LS": {"currency": "LSL", "callingCode": "266", "flag": "flag-ls", "name": {"common": "Lesotho", "deu": "Lesotho", "fra": "Lesotho", "hrv": "Lesoto", "ita": "Lesotho", "jpn": "レソト", "nld": "Lesotho", "por": "Lesoto", "rus": "Лесото", "spa": "Lesotho", "svk": "Lesotho", "fin": "Lesotho", "zho": "莱索托", "isr": "לסוטו"}}, "LR": {"currency": "LRD", "callingCode": "231", "flag": "flag-lr", "name": {"common": "Liberia", "deu": "Liberia", "fra": "Liberia", "hrv": "Liberija", "ita": "Liberia", "jpn": "リベリア", "nld": "Liberia", "por": "Libéria", "rus": "Либерия", "spa": "Liberia", "svk": "Libéria", "fin": "Liberia", "zho": "利比里亚", "isr": "ליבריה"}}, "LY": {"currency": "LYD", "callingCode": "218", "flag": "flag-ly", "name": {"common": "Libya", "deu": "Libyen", "fra": "Libye", "hrv": "Libija", "ita": "Libia", "jpn": "リビア", "nld": "Libië", "por": "Líbia", "rus": "Ливия", "spa": "Libia", "svk": "Líbya", "fin": "Libya", "zho": "利比亚", "isr": "לוב"}}, "LI": {"currency": "CHF", "callingCode": "423", "flag": "flag-li", "name": {"common": "Liechtenstein", "deu": "Liechtenstein", "fra": "Liechtenstein", "hrv": "Lihtenštajn", "ita": "Liechtenstein", "jpn": "リヒテンシュタイン", "nld": "Liechtenstein", "por": "Liechtenstein", "rus": "Лихтенштейн", "spa": "Liechtenstein", "svk": "Lichtenštajnsko", "fin": "Liechenstein", "zho": "列支敦士登", "isr": "ליכטנשטיין"}}, "LT": {"currency": "EUR", "callingCode": "370", "flag": "flag-lt", "name": {"common": "Lithuania", "deu": "Litauen", "fra": "Lituanie", "hrv": "Litva", "ita": "Lituania", "jpn": "リトアニア", "nld": "Litouwen", "por": "Lituânia", "rus": "Литва", "spa": "Lituania", "svk": "Litva", "fin": "Liettua", "zho": "立陶宛", "isr": "ליטא"}}, "LU": {"currency": "EUR", "callingCode": "352", "flag": "flag-lu", "name": {"common": "Luxembourg", "deu": "Luxemburg", "fra": "Luxembourg", "hrv": "Luksemburg", "ita": "Lussemburgo", "jpn": "ルクセンブルク", "nld": "Luxemburg", "por": "Luxemburgo", "rus": "Люксембург", "spa": "Luxemburgo", "svk": "Luxembursko", "fin": "Luxemburg", "zho": "卢森堡", "isr": "לוקסמבורג"}}, "MO": {"currency": "MOP", "callingCode": "853", "flag": "flag-mo", "name": {"common": "Macau", "deu": "Macao", "fra": "Macao", "hrv": "Makao", "ita": "Macao", "jpn": "マカオ", "nld": "Macao", "por": "Macau", "rus": "Макао", "spa": "Macao", "fin": "Macao", "isr": "מקאו (מחוז מנהלי מיוחד של סין)"}}, "MK": {"currency": "MKD", "callingCode": "389", "flag": "flag-mk", "name": {"common": "Macedonia", "deu": "Mazedonien", "fra": "Macédoine", "hrv": "Makedonija", "ita": "Macedonia", "jpn": "マケドニア旧ユーゴスラビア共和国", "nld": "Macedonië", "por": "Macedónia", "rus": "Республика Македония", "spa": "Macedonia", "svk": "Macedónsko", "fin": "Makedonia", "zho": "马其顿", "isr": "מקדוניה"}}, "MG": {"currency": "MGA", "callingCode": "261", "flag": "flag-mg", "name": {"common": "Madagascar", "deu": "Madagaskar", "fra": "Madagascar", "hrv": "Madagaskar", "ita": "Madagascar", "jpn": "マダガスカル", "nld": "Madagaskar", "por": "Madagáscar", "rus": "Мадагаскар", "spa": "Madagascar", "svk": "Madagaskar", "fin": "Madagaskar", "zho": "马达加斯加", "isr": "מדגסקר"}}, "MW": {"currency": "MWK", "callingCode": "265", "flag": "flag-mw", "name": {"common": "Malawi", "deu": "Malawi", "fra": "Malawi", "hrv": "Malavi", "ita": "Malawi", "jpn": "マラウイ", "nld": "Malawi", "por": "Malawi", "rus": "Малави", "spa": "Malawi", "svk": "Malawi", "fin": "Malawi", "zho": "马拉维", "isr": "מלאווי"}}, "MY": {"currency": "MYR", "callingCode": "60", "flag": "flag-my", "name": {"common": "Malaysia", "deu": "Malaysia", "fra": "Malaisie", "hrv": "Malezija", "ita": "Malesia", "jpn": "マレーシア", "nld": "Maleisië", "por": "Malásia", "rus": "Малайзия", "spa": "Malasia", "svk": "Malajzia", "fin": "Malesia", "zho": "马来西亚", "isr": "מלזיה"}}, "MV": {"currency": "MVR", "callingCode": "960", "flag": "flag-mv", "name": {"common": "Maldives", "deu": "Malediven", "fra": "Maldives", "hrv": "Maldivi", "ita": "Maldive", "jpn": "モルディブ", "nld": "Maldiven", "por": "Maldivas", "rus": "Мальдивы", "spa": "Maldivas", "svk": "Maldivy", "fin": "Malediivit", "zho": "马尔代夫", "isr": "האיים המלדיביים"}}, "ML": {"currency": "XOF", "callingCode": "223", "flag": "flag-ml", "name": {"common": "Mali", "deu": "Mali", "fra": "Mali", "hrv": "Mali", "ita": "Mali", "jpn": "マリ", "nld": "Mali", "por": "Mali", "rus": "Мали", "spa": "Mali", "svk": "Mali", "fin": "Mali", "zho": "马里", "isr": "מאלי"}}, "MT": {"currency": "EUR", "callingCode": "356", "flag": "flag-mt", "name": {"common": "Malta", "deu": "Malta", "fra": "Malte", "hrv": "Malta", "ita": "Malta", "jpn": "マルタ", "nld": "Malta", "por": "Malta", "rus": "Мальта", "spa": "Malta", "svk": "Malta", "fin": "Malta", "zho": "马耳他", "isr": "מלטה"}}, "MH": {"currency": "USD", "callingCode": "692", "flag": "flag-mh", "name": {"common": "Marshall Islands", "deu": "Marshallinseln", "fra": "Îles Marshall", "hrv": "Maršalovi Otoci", "ita": "Isole Marshall", "jpn": "マーシャル諸島", "nld": "Marshalleilanden", "por": "Ilhas Marshall", "rus": "Маршалловы Острова", "spa": "Islas Marshall", "svk": "Marshallove ostrovy", "fin": "Marshallinsaaret", "zho": "马绍尔群岛", "isr": "איי מרשל"}}, "MQ": {"currency": "EUR", "callingCode": "596", "flag": "flag-mq", "name": {"common": "Martinique", "deu": "Martinique", "fra": "Martinique", "hrv": "Martinique", "ita": "Martinica", "jpn": "マルティニーク", "nld": "Martinique", "por": "Martinica", "rus": "Мартиника", "spa": "Martinica", "svk": "Martinik", "fin": "Martinique", "zho": "马提尼克", "isr": "מרטיניק"}}, "MR": {"currency": "MRO", "callingCode": "222", "flag": "flag-mr", "name": {"common": "Mauritania", "deu": "Mauretanien", "fra": "Mauritanie", "hrv": "Mauritanija", "ita": "Mauritania", "jpn": "モーリタニア", "nld": "Mauritanië", "por": "Mauritânia", "rus": "Мавритания", "spa": "Mauritania", "svk": "Mauritánia", "fin": "Mauritania", "zho": "毛里塔尼亚", "isr": "מאוריטניה"}}, "MU": {"currency": "MUR", "callingCode": "230", "flag": "flag-mu", "name": {"common": "Mauritius", "deu": "Mauritius", "fra": "Île Maurice", "hrv": "Mauricijus", "ita": "Mauritius", "jpn": "モーリシャス", "nld": "Mauritius", "por": "Maurício", "rus": "Маврикий", "spa": "Mauricio", "svk": "Maurícius", "fin": "Mauritius", "zho": "毛里求斯", "isr": "מאוריציוס"}}, "YT": {"currency": "EUR", "callingCode": "262", "flag": "flag-yt", "name": {"common": "Mayotte", "deu": "Mayotte", "fra": "Mayotte", "hrv": "Mayotte", "ita": "Mayotte", "jpn": "マヨット", "nld": "Mayotte", "por": "Mayotte", "rus": "Майотта", "spa": "Mayotte", "svk": "Mayotte", "fin": "Mayotte", "zho": "马约特", "isr": "מאיוט"}}, "MX": {"currency": "MXN", "callingCode": "52", "flag": "flag-mx", "name": {"common": "Mexico", "deu": "Mexiko", "fra": "Mexique", "hrv": "Meksiko", "ita": "Messico", "jpn": "メキシコ", "nld": "Mexico", "por": "México", "rus": "Мексика", "spa": "México", "svk": "Mexiko", "fin": "Meksiko", "zho": "墨西哥", "isr": "מקסיקו"}}, "FM": {"currency": "USD", "callingCode": "691", "flag": "flag-fm", "name": {"common": "Micronesia", "deu": "Mikronesien", "fra": "Micronésie", "hrv": "Mikronezija", "ita": "Micronesia", "jpn": "ミクロネシア連邦", "nld": "Micronesië", "por": "Micronésia", "rus": "Федеративные Штаты Микронезии", "spa": "Micronesia", "svk": "Mikronézia", "fin": "Mikronesia", "zho": "密克罗尼西亚", "isr": "מיקרונזיה"}}, "MD": {"currency": "MDL", "callingCode": "373", "flag": "flag-md", "name": {"common": "Moldova", "deu": "Moldawie", "fra": "Moldavie", "hrv": "Moldova", "ita": "Moldavia", "jpn": "モルドバ共和国", "nld": "Moldavië", "por": "Moldávia", "rus": "Молдавия", "spa": "Moldavia", "svk": "Moldavsko", "fin": "Moldova", "zho": "摩尔多瓦", "isr": "מולדובה"}}, "MC": {"currency": "EUR", "callingCode": "377", "flag": "flag-mc", "name": {"common": "Monaco", "deu": "Monaco", "fra": "Monaco", "hrv": "Monako", "ita": "Principato di Monaco", "jpn": "モナコ", "nld": "Monaco", "por": "Mónaco", "rus": "Монако", "spa": "Mónaco", "svk": "Monako", "fin": "Monaco", "zho": "摩纳哥", "isr": "מונקו"}}, "MN": {"currency": "MNT", "callingCode": "976", "flag": "flag-mn", "name": {"common": "Mongolia", "deu": "Mongolei", "fra": "Mongolie", "hrv": "Mongolija", "ita": "Mongolia", "jpn": "モンゴル", "nld": "Mongolië", "por": "Mongólia", "rus": "Монголия", "spa": "Mongolia", "svk": "Mongolsko", "fin": "Mongolia", "zho": "蒙古", "isr": "מונגוליה"}}, "ME": {"currency": "EUR", "callingCode": "382", "flag": "flag-me", "name": {"common": "Montenegro", "deu": "Montenegro", "fra": "Monténégro", "hrv": "Crna Gora", "ita": "Montenegro", "jpn": "モンテネグロ", "nld": "Montenegro", "por": "Montenegro", "rus": "Черногория", "spa": "Montenegro", "svk": "Čierna Hora", "fin": "Montenegro", "zho": "黑山", "isr": "מונטנגרו"}}, "MS": {"currency": "XCD", "callingCode": "1664", "flag": "flag-ms", "name": {"common": "Montserrat", "deu": "Montserrat", "fra": "Montserrat", "hrv": "Montserrat", "ita": "Montserrat", "jpn": "モントセラト", "nld": "Montserrat", "por": "Montserrat", "rus": "Монтсеррат", "spa": "Montserrat", "svk": "Montserrat", "fin": "Montserrat", "zho": "蒙特塞拉特", "isr": "מונסראט"}}, "MA": {"currency": "MAD", "callingCode": "212", "flag": "flag-ma", "name": {"common": "Morocco", "deu": "Marokko", "fra": "Maroc", "hrv": "Maroko", "ita": "Marocco", "jpn": "モロッコ", "nld": "Marokko", "por": "Marrocos", "rus": "Марокко", "spa": "Marruecos", "svk": "Maroko", "fin": "Marokko", "zho": "摩洛哥", "isr": "מרוקו"}}, "MZ": {"currency": "MZN", "callingCode": "258", "flag": "flag-mz", "name": {"common": "Mozambique", "deu": "Mosambik", "fra": "Mozambique", "hrv": "Mozambik", "ita": "Mozambico", "jpn": "モザンビーク", "nld": "Mozambique", "por": "Moçambique", "rus": "Мозамбик", "spa": "Mozambique", "svk": "Mozambik", "fin": "Mosambik", "zho": "莫桑比克", "isr": "מוזמביק"}}, "MM": {"currency": "MMK", "callingCode": "95", "flag": "flag-mm", "name": {"common": "Myanmar", "deu": "Myanmar", "fra": "Birmanie", "hrv": "Mijanmar", "ita": "Birmania", "jpn": "ミャンマー", "nld": "Myanmar", "por": "Myanmar", "rus": "Мьянма", "spa": "Myanmar", "svk": "Mjanmarsko", "fin": "Myanmar", "zho": "缅甸", "isr": "מיאנמר (בורמה)"}}, "NA": {"currency": "NAD", "callingCode": "264", "flag": "flag-na", "name": {"common": "Namibia", "deu": "Namibia", "fra": "Namibie", "hrv": "Namibija", "ita": "Namibia", "jpn": "ナミビア", "nld": "Namibië", "por": "Namíbia", "rus": "Намибия", "spa": "Namibia", "svk": "Namíbia", "fin": "Namibia", "zho": "纳米比亚", "isr": "נמיביה"}}, "NR": {"currency": "AUD", "callingCode": "674", "flag": "flag-nr", "name": {"common": "Nauru", "deu": "Nauru", "fra": "Nauru", "hrv": "Nauru", "ita": "Nauru", "jpn": "ナウル", "nld": "Nauru", "por": "Nauru", "rus": "Науру", "spa": "Nauru", "svk": "Nauru", "fin": "Nauru", "zho": "瑙鲁", "isr": "נאורו"}}, "NP": {"currency": "NPR", "callingCode": "977", "flag": "flag-np", "name": {"common": "Nepal", "deu": "Népal", "fra": "Népal", "hrv": "Nepal", "ita": "Nepal", "jpn": "ネパール", "nld": "Nepal", "por": "Nepal", "rus": "Непал", "spa": "Nepal", "svk": "Nepál", "fin": "Nepal", "zho": "尼泊尔", "isr": "נפאל"}}, "NL": {"currency": "EUR", "callingCode": "31", "flag": "flag-nl", "name": {"common": "Netherlands", "deu": "Niederlande", "fra": "Pays-Bas", "hrv": "Nizozemska", "ita": "Paesi Bassi", "jpn": "オランダ", "nld": "Nederland", "por": "Holanda", "rus": "Нидерланды", "spa": "Países Bajos", "svk": "Holansko", "fin": "Alankomaat", "zho": "荷兰", "isr": "הולנד"}}, "NC": {"currency": "XPF", "callingCode": "687", "flag": "flag-nc", "name": {"common": "New Caledonia", "deu": "Neukaledonien", "fra": "Nouvelle-Calédonie", "hrv": "Nova Kaledonija", "ita": "Nuova Caledonia", "jpn": "ニューカレドニア", "nld": "Nieuw-Caledonië", "por": "Nova Caledónia", "rus": "Новая Каледония", "spa": "Nueva Caledonia", "svk": "Nová Kaledónia", "fin": "Uusi-Kaledonia", "zho": "新喀里多尼亚", "isr": "קלדוניה החדשה"}}, "NZ": {"currency": "NZD", "callingCode": "64", "flag": "flag-nz", "name": {"common": "New Zealand", "deu": "Neuseeland", "fra": "Nouvelle-Zélande", "hrv": "Novi Zeland", "ita": "Nuova Zelanda", "jpn": "ニュージーランド", "nld": "Nieuw-Zeeland", "por": "Nova Zelândia", "rus": "Новая Зеландия", "spa": "Nueva Zelanda", "svk": "Nový Zéland", "fin": "Uusi-Seelanti", "zho": "新西兰", "isr": "ניו זילנד"}}, "NI": {"currency": "NIO", "callingCode": "505", "flag": "flag-ni", "name": {"common": "Nicaragua", "deu": "Nicaragua", "fra": "Nicaragua", "hrv": "Nikaragva", "ita": "Nicaragua", "jpn": "ニカラグア", "nld": "Nicaragua", "por": "Nicarágua", "rus": "Никарагуа", "spa": "Nicaragua", "svk": "Nikaragua", "fin": "Nicaragua", "zho": "尼加拉瓜", "isr": "ניקרגואה"}}, "NE": {"currency": "XOF", "callingCode": "227", "flag": "flag-ne", "name": {"common": "Niger", "deu": "Niger", "fra": "Niger", "hrv": "Niger", "ita": "Niger", "jpn": "ニジェール", "nld": "Niger", "por": "Níger", "rus": "Нигер", "spa": "Níger", "svk": "Niger", "fin": "Niger", "zho": "尼日尔", "isr": "ניז׳ר"}}, "NG": {"currency": "NGN", "callingCode": "234", "flag": "flag-ng", "name": {"common": "Nigeria", "deu": "Nigeria", "fra": "Nigéria", "hrv": "Nigerija", "ita": "Nigeria", "jpn": "ナイジェリア", "nld": "Nigeria", "por": "Nigéria", "rus": "Нигерия", "spa": "Nigeria", "svk": "Nigéria", "fin": "Nigeria", "zho": "尼日利亚", "isr": "ניגריה"}}, "NU": {"currency": "NZD", "callingCode": "683", "flag": "flag-nu", "name": {"common": "Niue", "deu": "Niue", "fra": "Niue", "hrv": "Niue", "ita": "Niue", "jpn": "ニウエ", "nld": "Niue", "por": "Niue", "rus": "Ниуэ", "spa": "Niue", "svk": "Niue", "fin": "Niue", "zho": "纽埃", "isr": "ניווה"}}, "NF": {"currency": "AUD", "callingCode": "672", "flag": "flag-nf", "name": {"common": "Norfolk Island", "deu": "Norfolkinsel", "fra": "Île Norfolk", "hrv": "Otok Norfolk", "ita": "Isola Norfolk", "jpn": "ノーフォーク島", "nld": "Norfolkeiland", "por": "Ilha Norfolk", "rus": "Норфолк", "spa": "Isla de Norfolk", "svk": "Norfolk", "fin": "Norfolkinsaari", "zho": "诺福克岛", "isr": "איי נורפוק"}}, "KP": {"currency": "KPW", "callingCode": "850", "flag": "flag-kp", "name": {"common": "North Korea", "deu": "Nordkorea", "fra": "Corée du Nord", "hrv": "Sjeverna Koreja", "ita": "Corea del Nord", "jpn": "朝鮮民主主義人民共和国", "nld": "Noord-Korea", "por": "Coreia do Norte", "rus": "Северная Корея", "spa": "Corea del Norte", "svk": "Kórejská ľudovodemokratická republika (KĽR, Severná Kó)", "fin": "Pohjois-Korea", "zho": "朝鲜", "isr": "קוריאה הצפונית"}}, "MP": {"currency": "USD", "callingCode": "1670", "flag": "flag-mp", "name": {"common": "Northern Mariana Islands", "deu": "Nördliche Marianen", "fra": "Îles Mariannes du Nord", "hrv": "Sjevernomarijanski otoci", "ita": "Isole Marianne Settentrionali", "jpn": "北マリアナ諸島", "nld": "Noordelijke Marianeneilanden", "por": "Marianas Setentrionais", "rus": "Северные Марианские острова", "spa": "Islas Marianas del Norte", "svk": "Severné Mariány", "fin": "Pohjois-Mariaanit", "zho": "北马里亚纳群岛", "isr": "איי מריאנה הצפוניים"}}, "NO": {"currency": "NOK", "callingCode": "47", "flag": "flag-no", "name": {"common": "Norway", "deu": "Norwegen", "fra": "Norvège", "hrv": "Norveška", "ita": "Norvegia", "jpn": "ノルウェー", "nld": "Noorwegen", "por": "Noruega", "rus": "Норвегия", "spa": "Noruega", "svk": "Nórsko", "fin": "Norja", "zho": "挪威", "isr": "נורווגיה"}}, "OM": {"currency": "OMR", "callingCode": "968", "flag": "flag-om", "name": {"common": "Oman", "deu": "Oman", "fra": "Oman", "hrv": "Oman", "ita": "oman", "jpn": "オマーン", "nld": "Oman", "por": "Omã", "rus": "Оман", "spa": "Omán", "svk": "Omán", "fin": "Oman", "zho": "阿曼", "isr": "עומאן"}}, "PK": {"currency": "PKR", "callingCode": "92", "flag": "flag-pk", "name": {"common": "Pakistan", "deu": "Pakistan", "fra": "Pakistan", "hrv": "Pakistan", "ita": "Pakistan", "jpn": "パキスタン", "nld": "Pakistan", "por": "Paquistão", "rus": "Пакистан", "spa": "Pakistán", "svk": "Pakistan", "fin": "Pakistan", "zho": "巴基斯坦", "isr": "פקיסטן"}}, "PW": {"currency": "USD", "callingCode": "680", "flag": "flag-pw", "name": {"common": "Palau", "deu": "Palau", "fra": "Palaos (Palau)", "hrv": "Palau", "ita": "Palau", "jpn": "パラオ", "nld": "Palau", "por": "Palau", "rus": "Палау", "spa": "Palau", "svk": "Palau", "fin": "Palau", "zho": "帕劳", "isr": "פלאו"}}, "PS": {"currency": "ILS", "callingCode": "970", "flag": "flag-ps", "name": {"common": "Palestine", "deu": "Palästina", "fra": "Palestine", "hrv": "Palestina", "ita": "Palestina", "jpn": "パレスチナ", "nld": "Palestijnse gebieden", "por": "Palestina", "rus": "Палестина", "spa": "Palestina", "svk": "Palestína", "fin": "Palestiina", "zho": "巴勒斯坦", "isr": "השטחים הפלסטיניים"}}, "PA": {"currency": "PAB", "callingCode": "507", "flag": "flag-pa", "name": {"common": "Panama", "deu": "Panama", "fra": "Panama", "hrv": "Panama", "ita": "Panama", "jpn": "パナマ", "nld": "Panama", "por": "Panamá", "rus": "Панама", "spa": "Panamá", "svk": "Panama", "fin": "Panama", "zho": "巴拿马", "isr": "פנמה"}}, "PG": {"currency": "PGK", "callingCode": "675", "flag": "flag-pg", "name": {"common": "Papua New Guinea", "deu": "Papua-Neuguinea", "fra": "Papouasie-Nouvelle-Guinée", "hrv": "Papua Nova Gvineja", "ita": "Papua Nuova Guinea", "jpn": "パプアニューギニア", "nld": "Papoea-Nieuw-Guinea", "por": "Papua Nova Guiné", "rus": "Папуа — Новая Гвинея", "spa": "Papúa Nueva Guinea", "svk": "Papua-Nová Guinea", "fin": "Papua-Uusi-Guinea", "zho": "巴布亚新几内亚", "isr": "פפואה גינאה החדשה"}}, "PY": {"currency": "PYG", "callingCode": "595", "flag": "flag-py", "name": {"common": "Paraguay", "deu": "Paraguay", "fra": "Paraguay", "hrv": "Paragvaj", "ita": "Paraguay", "jpn": "パラグアイ", "nld": "Paraguay", "por": "Paraguai", "rus": "Парагвай", "spa": "Paraguay", "svk": "Paraguaj", "fin": "Paraguay", "zho": "巴拉圭", "isr": "פרגוואי"}}, "PE": {"currency": "PEN", "callingCode": "51", "flag": "flag-pe", "name": {"common": "Peru", "deu": "Peru", "fra": "Pérou", "hrv": "Peru", "ita": "Perù", "jpn": "ペルー", "nld": "Peru", "por": "Perú", "rus": "Перу", "spa": "Perú", "svk": "Peru", "fin": "Peru", "zho": "秘鲁", "isr": "פרו"}}, "PH": {"currency": "PHP", "callingCode": "63", "flag": "flag-ph", "name": {"common": "Philippines", "deu": "Philippinen", "fra": "Philippines", "hrv": "Filipini", "ita": "Filippine", "jpn": "フィリピン", "nld": "Filipijnen", "por": "Filipinas", "rus": "Филиппины", "spa": "Filipinas", "svk": "Filipíny", "fin": "Filippiinit", "zho": "菲律宾", "isr": "הפיליפינים"}}, "PN": {"currency": "NZD", "callingCode": "64", "flag": "flag-pn", "name": {"common": "Pitcairn Islands", "deu": "Pitcairn", "fra": "Îles Pitcairn", "hrv": "Pitcairnovo otočje", "ita": "Isole Pitcairn", "jpn": "ピトケアン", "nld": "Pitcairneilanden", "por": "Ilhas Pitcairn", "rus": "Острова Питкэрн", "spa": "Islas Pitcairn", "svk": "Pitcairnove ostrovy", "fin": "Pitcairn", "zho": "皮特凯恩群岛", "isr": "איי פיטקרן"}}, "PL": {"currency": "PLN", "callingCode": "48", "flag": "flag-pl", "name": {"common": "Poland", "deu": "Polen", "fra": "Pologne", "hrv": "Poljska", "ita": "Polonia", "jpn": "ポーランド", "nld": "Polen", "por": "Polónia", "rus": "Польша", "spa": "Polonia", "svk": "Poľsko", "fin": "Puola", "zho": "波兰", "isr": "פולין"}}, "PT": {"currency": "EUR", "callingCode": "351", "flag": "flag-pt", "name": {"common": "Portugal", "deu": "Portugal", "fra": "Portugal", "hrv": "Portugal", "ita": "Portogallo", "jpn": "ポルトガル", "nld": "Portugal", "por": "Portugal", "rus": "Португалия", "spa": "Portugal", "svk": "Portugalsko", "fin": "Portugali", "zho": "葡萄牙", "isr": "פורטוגל"}}, "PR": {"currency": "USD", "callingCode": "1787", "flag": "flag-pr", "name": {"common": "Puerto Rico", "deu": "Puerto Rico", "fra": "Porto Rico", "hrv": "Portoriko", "ita": "Porto Rico", "jpn": "プエルトリコ", "nld": "Puerto Rico", "por": "Porto Rico", "rus": "Пуэрто-Рико", "spa": "Puerto Rico", "svk": "Portoriko", "fin": "Puerto Rico", "zho": "波多黎各", "isr": "פוארטו ריקו"}}, "QA": {"currency": "QAR", "callingCode": "974", "flag": "flag-qa", "name": {"common": "Qatar", "deu": "Katar", "fra": "Qatar", "hrv": "Katar", "ita": "Qatar", "jpn": "カタール", "nld": "Qatar", "por": "Catar", "rus": "Катар", "spa": "Catar", "svk": "Katar", "fin": "Qatar", "zho": "卡塔尔", "isr": "קטאר"}}, "CG": {"currency": "XAF", "callingCode": "242", "flag": "flag-cg", "name": {"common": "Republic of the Congo", "cym": "Gweriniaeth y Congo", "deu": "Kongo", "fra": "Congo", "hrv": "Kongo", "ita": "Congo", "jpn": "コンゴ共和国", "nld": "Congo", "por": "Congo", "rus": "Республика Конго", "spa": "Congo", "svk": "Kongo", "fin": "Kongo-Brazzaville", "zho": "刚果", "isr": "קונגו - ברזאויל"}}, "RO": {"currency": "RON", "callingCode": "40", "flag": "flag-ro", "name": {"common": "Romania", "deu": "Rumänien", "fra": "Roumanie", "hrv": "Rumunjska", "ita": "Romania", "jpn": "ルーマニア", "nld": "Roemenië", "por": "Roménia", "rus": "Румыния", "spa": "Rumania", "svk": "Rumunsko", "fin": "Romania", "zho": "罗马尼亚", "isr": "רומניה"}}, "RU": {"currency": "RUB", "callingCode": "7", "flag": "flag-ru", "name": {"common": "Russia", "deu": "Russland", "fra": "Russie", "hrv": "Rusija", "ita": "Russia", "jpn": "ロシア連邦", "nld": "Rusland", "por": "Rússia", "rus": "Россия", "spa": "Rusia", "svk": "Rusko", "fin": "Venäjä", "zho": "俄罗斯", "isr": "רוסיה"}}, "RW": {"currency": "RWF", "callingCode": "250", "flag": "flag-rw", "name": {"common": "Rwanda", "deu": "Ruanda", "fra": "Rwanda", "hrv": "Ruanda", "ita": "Ruanda", "jpn": "ルワンダ", "nld": "Rwanda", "por": "Ruanda", "rus": "Руанда", "spa": "Ruanda", "svk": "Rwanda", "fin": "Ruanda", "zho": "卢旺达", "isr": "רואנדה"}}, "RE": {"currency": "EUR", "callingCode": "262", "flag": "flag-re", "name": {"common": "Réunion", "deu": "Réunion", "fra": "Réunion", "hrv": "Réunion", "ita": "Riunione", "jpn": "レユニオン", "nld": "Réunion", "por": "Reunião", "rus": "Реюньон", "spa": "Reunión", "svk": "Réunion", "fin": "Réunion", "zho": "留尼旺岛", "isr": "ראוניון"}}, "BL": {"currency": "EUR", "callingCode": "590", "flag": "flag-bl", "name": {"common": "Saint Barthélemy", "deu": "Saint-Barthélemy", "fra": "Saint-Barthélemy", "hrv": "Saint Barthélemy", "ita": "Antille Francesi", "jpn": "サン・バルテルミー", "nld": "Saint Barthélemy", "por": "São Bartolomeu", "rus": "Сен-Бартелеми", "spa": "San Bartolomé", "svk": "Svätý Bartolomej", "fin": "Saint-Barthélemy", "zho": "圣巴泰勒米", "isr": "סנט ברתולומיאו"}}, "KN": {"currency": "XCD", "callingCode": "1869", "flag": "flag-kn", "name": {"common": "Saint Kitts and Nevis", "deu": "Saint Christopher und Nevis", "fra": "Saint-Christophe-et-Niévès", "hrv": "Sveti Kristof i Nevis", "ita": "Saint Kitts e Nevis", "jpn": "セントクリストファー・ネイビス", "nld": "Saint Kitts en Nevis", "por": "São Cristóvão e Nevis", "rus": "Сент-Китс и Невис", "spa": "San Cristóbal y Nieves", "svk": "Svätý Krištof a Nevis", "fin": "Saint Kitts ja Nevis", "zho": "圣基茨和尼维斯", "isr": "סנט קיטס ונוויס"}}, "LC": {"currency": "XCD", "callingCode": "1758", "flag": "flag-lc", "name": {"common": "Saint Lucia", "deu": "Saint Lucia", "fra": "Sainte-Lucie", "hrv": "Sveta Lucija", "ita": "Santa Lucia", "jpn": "セントルシア", "nld": "Saint Lucia", "por": "Santa Lúcia", "rus": "Сент-Люсия", "spa": "Santa Lucía", "svk": "Svätá Lucia", "fin": "Saint Lucia", "zho": "圣卢西亚", "isr": "סנט לוסיה"}}, "MF": {"currency": "EUR", "callingCode": "590", "flag": "flag-mf", "name": {"common": "Saint Martin", "deu": "Saint Martin", "fra": "Saint-Martin", "hrv": "Sveti Martin", "ita": "Saint Martin", "jpn": "サン・マルタン(フランス領)", "nld": "Saint-Martin", "por": "São Martinho", "rus": "Сен-Мартен", "spa": "Saint Martin", "svk": "Svätý Martin", "fin": "Saint-Martin", "zho": "圣马丁", "isr": "סן מרטן"}}, "PM": {"currency": "EUR", "callingCode": "508", "flag": "flag-pm", "name": {"common": "Saint Pierre and Miquelon", "deu": "Saint-Pierre und Miquelon", "fra": "Saint-Pierre-et-Miquelon", "hrv": "Sveti Petar i Mikelon", "ita": "Saint-Pierre e Miquelon", "jpn": "サンピエール島・ミクロン島", "nld": "Saint Pierre en Miquelon", "por": "Saint-Pierre e Miquelon", "rus": "Сен-Пьер и Микелон", "spa": "San Pedro y Miquelón", "svk": "Saint Pierre a Miquelon", "fin": "Saint-Pierre ja Miquelon", "zho": "圣皮埃尔和密克隆", "isr": "סנט פייר ומיקלון"}}, "VC": {"currency": "XCD", "callingCode": "1784", "flag": "flag-vc", "name": {"common": "Saint Vincent and the Grenadines", "deu": "Saint Vincent und die Grenadinen", "fra": "Saint-Vincent-et-les-Grenadines", "hrv": "Sveti Vincent i Grenadini", "ita": "Saint Vincent e Grenadine", "jpn": "セントビンセントおよびグレナディーン諸島", "nld": "Saint Vincent en de Grenadines", "por": "São Vincente e Granadinas", "rus": "Сент-Винсент и Гренадины", "spa": "San Vicente y Granadinas", "svk": "Svätý Vincent a Grenadíny", "fin": "Saint Vincent ja Grenadiinit", "zho": "圣文森特和格林纳丁斯", "isr": "סנט וינסנט והגרנדינים"}}, "WS": {"currency": "WST", "callingCode": "685", "flag": "flag-ws", "name": {"common": "Samoa", "deu": "Samoa", "fra": "Samoa", "hrv": "Samoa", "ita": "Samoa", "jpn": "サモア", "nld": "Samoa", "por": "Samoa", "rus": "Самоа", "spa": "Samoa", "fin": "Samoa", "zho": "萨摩亚", "isr": "סמואה"}}, "SM": {"currency": "EUR", "callingCode": "378", "flag": "flag-sm", "name": {"common": "San Marino", "deu": "San Marino", "fra": "Saint-Marin", "hrv": "San Marino", "ita": "San Marino", "jpn": "サンマリノ", "nld": "San Marino", "por": "San Marino", "rus": "Сан-Марино", "spa": "San Marino", "svk": "San Maríno", "fin": "San Marino", "zho": "圣马力诺", "isr": "סן מרינו"}}, "SA": {"currency": "SAR", "callingCode": "966", "flag": "flag-sa", "name": {"common": "Saudi Arabia", "deu": "Saudi-Arabien", "fra": "Arabie Saoudite", "hrv": "Saudijska Arabija", "ita": "Arabia Saudita", "jpn": "サウジアラビア", "nld": "Saoedi-Arabië", "por": "Arábia Saudita", "rus": "Саудовская Аравия", "spa": "Arabia Saudí", "svk": "Saudská Arábia", "fin": "Saudi-Arabia", "zho": "沙特阿拉伯", "isr": "ערב הסעודית"}}, "SN": {"currency": "XOF", "callingCode": "221", "flag": "flag-sn", "name": {"common": "Senegal", "deu": "Senegal", "fra": "Sénégal", "hrv": "Senegal", "ita": "Senegal", "jpn": "セネガル", "nld": "Senegal", "por": "Senegal", "rus": "Сенегал", "spa": "Senegal", "svk": "Senegal", "fin": "Senegal", "zho": "塞内加尔", "isr": "סנגל"}}, "RS": {"currency": "RSD", "callingCode": "381", "flag": "flag-rs", "name": {"common": "Serbia", "deu": "Serbien", "fra": "Serbie", "hrv": "Srbija", "ita": "Serbia", "jpn": "セルビア", "nld": "Servië", "por": "Sérvia", "rus": "Сербия", "spa": "Serbia", "svk": "Srbsko", "fin": "Serbia", "zho": "塞尔维亚", "isr": "סרביה"}}, "SC": {"currency": "SCR", "callingCode": "248", "flag": "flag-sc", "name": {"common": "Seychelles", "deu": "Seychellen", "fra": "Seychelles", "hrv": "Sejšeli", "ita": "Seychelles", "jpn": "セーシェル", "nld": "Seychellen", "por": "Seicheles", "rus": "Сейшельские Острова", "spa": "Seychelles", "svk": "Seychely", "fin": "Seychellit", "zho": "塞舌尔", "isr": "איי סיישל"}}, "SL": {"currency": "SLL", "callingCode": "232", "flag": "flag-sl", "name": {"common": "Sierra Leone", "deu": "Sierra Leone", "fra": "Sierra Leone", "hrv": "Sijera Leone", "ita": "Sierra Leone", "jpn": "シエラレオネ", "nld": "Sierra Leone", "por": "Serra Leoa", "rus": "Сьерра-Леоне", "spa": "Sierra Leone", "svk": "Sierra Leone", "fin": "Sierra Leone", "zho": "塞拉利昂", "isr": "סיירה לאונה"}}, "SG": {"currency": "SGD", "callingCode": "65", "flag": "flag-sg", "name": {"common": "Singapore", "deu": "Singapur", "fra": "Singapour", "hrv": "Singapur", "ita": "Singapore", "jpn": "シンガポール", "nld": "Singapore", "por": "Singapura", "rus": "Сингапур", "spa": "Singapur", "svk": "Singapur", "fin": "Singapore", "isr": "סינגפור"}}, "SX": {"currency": "ANG", "callingCode": "1721", "flag": "flag-sx", "name": {"common": "Sint Maarten", "deu": "Sint Maarten", "fra": "Saint-Martin", "ita": "Sint Maarten", "jpn": "シント・マールテン", "nld": "Sint Maarten", "por": "São Martinho", "rus": "Синт-Мартен", "spa": "Sint Maarten", "svk": "Svätý Martin", "fin": "Sint Maarten", "zho": "圣马丁岛", "isr": "סנט מארטן"}}, "SK": {"currency": "EUR", "callingCode": "421", "flag": "flag-sk", "name": {"common": "Slovakia", "deu": "Slowakei", "fra": "Slovaquie", "hrv": "Slovačka", "ita": "Slovacchia", "jpn": "スロバキア", "nld": "Slowakije", "por": "Eslováquia", "rus": "Словакия", "spa": "República Eslovaca", "svk": "Slovensko", "fin": "Slovakia", "zho": "斯洛伐克", "isr": "סלובקיה"}}, "SI": {"currency": "EUR", "callingCode": "386", "flag": "flag-si", "name": {"common": "Slovenia", "deu": "Slowenien", "fra": "Slovénie", "hrv": "Slovenija", "ita": "Slovenia", "jpn": "スロベニア", "nld": "Slovenië", "por": "Eslovénia", "rus": "Словения", "spa": "Eslovenia", "svk": "Slovinsko", "fin": "Slovenia", "zho": "斯洛文尼亚", "isr": "סלובניה"}}, "SB": {"currency": "SBD", "callingCode": "677", "flag": "flag-sb", "name": {"common": "Solomon Islands", "deu": "Salomonen", "fra": "Îles Salomon", "hrv": "Solomonski Otoci", "ita": "Isole Salomone", "jpn": "ソロモン諸島", "nld": "Salomonseilanden", "por": "Ilhas Salomão", "rus": "Соломоновы Острова", "spa": "Islas Salomón", "svk": "Salomonove ostrovy", "fin": "Salomonsaaret", "zho": "所罗门群岛", "isr": "איי שלמה"}}, "SO": {"currency": "SOS", "callingCode": "252", "flag": "flag-so", "name": {"common": "Somalia", "deu": "Somalia", "fra": "Somalie", "hrv": "Somalija", "ita": "Somalia", "jpn": "ソマリア", "nld": "Somalië", "por": "Somália", "rus": "Сомали", "spa": "Somalia", "svk": "Somálsko", "fin": "Somalia", "zho": "索马里", "isr": "סומליה"}}, "ZA": {"currency": "ZAR", "callingCode": "27", "flag": "flag-za", "name": {"common": "South Africa", "deu": "Republik Südafrika", "fra": "Afrique du Sud", "hrv": "Južnoafrička Republika", "ita": "Sud Africa", "jpn": "南アフリカ", "nld": "Zuid-Afrika", "por": "África do Sul", "rus": "Южно-Африканская Республика", "spa": "República de Sudáfrica", "svk": "Juhoafrická republika", "fin": "Etelä-Afrikka", "zho": "南非", "isr": "דרום אפריקה"}}, "GS": {"currency": "GBP", "callingCode": "500", "flag": "flag-gs", "name": {"common": "South Georgia", "deu": "Südgeorgien und die Südlichen Sandwichinseln", "fra": "Géorgie du Sud-et-les Îles Sandwich du Sud", "hrv": "Južna Georgija i otočje Južni Sandwich", "ita": "Georgia del Sud e Isole Sandwich Meridionali", "jpn": "サウスジョージア・サウスサンドウィッチ諸島", "nld": "Zuid-Georgia en Zuidelijke Sandwicheilanden", "por": "Ilhas Geórgia do Sul e Sandwich do Sul", "rus": "Южная Георгия и Южные Сандвичевы острова", "spa": "Islas Georgias del Sur y Sandwich del Sur", "svk": "Južná Georgia a Južné Sandwichove ostrovy", "fin": "Etelä-Georgia ja Eteläiset Sandwichsaaret", "zho": "南乔治亚", "isr": "ג׳ורג׳יה הדרומית ואיי סנדוויץ׳ הדרומיים"}}, "KR": {"currency": "KRW", "callingCode": "82", "flag": "flag-kr", "name": {"common": "South Korea", "deu": "Südkorea", "fra": "Corée du Sud", "hrv": "Južna Koreja", "ita": "Corea del Sud", "jpn": "大韓民国", "nld": "Zuid-Korea", "por": "Coreia do Sul", "rus": "Южная Корея", "spa": "Corea del Sur", "svk": "Južná Kórea", "fin": "Etelä-Korea", "zho": "韩国", "isr": "קוריאה הדרומית"}}, "SS": {"currency": "SSP", "callingCode": "211", "flag": "flag-ss", "name": {"common": "South Sudan", "deu": "Südsudan", "fra": "Soudan du Sud", "hrv": "Južni Sudan", "ita": "Sudan del sud", "jpn": "南スーダン", "nld": "Zuid-Soedan", "por": "Sudão do Sul", "rus": "Южный Судан", "spa": "Sudán del Sur", "svk": "Južný Sudán", "fin": "Etelä-Sudan", "zho": "南苏丹", "isr": "דרום סודן"}}, "ES": {"currency": "EUR", "callingCode": "34", "flag": "flag-es", "name": {"common": "Spain", "deu": "Spanien", "fra": "Espagne", "hrv": "Španjolska", "ita": "Spagna", "jpn": "スペイン", "nld": "Spanje", "por": "Espanha", "rus": "Испания", "spa": "España", "svk": "Španielsko", "fin": "Espanja", "zho": "西班牙", "isr": "ספרד"}}, "LK": {"currency": "LKR", "callingCode": "94", "flag": "flag-lk", "name": {"common": "Sri Lanka", "deu": "Sri Lanka", "fra": "Sri Lanka", "hrv": "Šri Lanka", "ita": "Sri Lanka", "jpn": "スリランカ", "nld": "Sri Lanka", "por": "Sri Lanka", "rus": "Шри-Ланка", "spa": "Sri Lanka", "svk": "Srí Lanka", "fin": "Sri Lanka", "zho": "斯里兰卡", "isr": "סרי לנקה"}}, "SD": {"currency": "SDG", "callingCode": "249", "flag": "flag-sd", "name": {"common": "Sudan", "deu": "Sudan", "fra": "Soudan", "hrv": "Sudan", "ita": "Sudan", "jpn": "スーダン", "nld": "Soedan", "por": "Sudão", "rus": "Судан", "spa": "Sudán", "svk": "Sudán", "fin": "Sudan", "zho": "苏丹", "isr": "סודן"}}, "SR": {"currency": "SRD", "callingCode": "597", "flag": "flag-sr", "name": {"common": "Suriname", "deu": "Suriname", "fra": "Surinam", "hrv": "Surinam", "ita": "Suriname", "jpn": "スリナム", "nld": "Suriname", "por": "Suriname", "rus": "Суринам", "spa": "Surinam", "svk": "Surinam", "fin": "Suriname", "zho": "苏里南", "isr": "סורינם"}}, "SJ": {"currency": "NOK", "callingCode": "4779", "flag": "flag-sj", "name": {"common": "Svalbard and Jan Mayen", "deu": "Spitzbergen", "fra": "Svalbard et Jan Mayen", "hrv": "Svalbard i Jan Mayen", "ita": "Svalbard e Jan Mayen", "jpn": "スヴァールバル諸島およびヤンマイエン島", "nld": "Svalbard en Jan Mayen", "por": "Ilhas Svalbard e Jan Mayen", "rus": "Шпицберген и Ян-Майен", "spa": "Islas Svalbard y Jan Mayen", "svk": "Svalbard a Jan Mayen", "fin": "Huippuvuoret", "zho": "斯瓦尔巴特", "isr": "סוולבארד ויאן מאיין"}}, "SZ": {"currency": "SZL", "callingCode": "268", "flag": "flag-sz", "name": {"common": "Swaziland", "deu": "Swasiland", "fra": "Swaziland", "hrv": "Svazi", "ita": "Swaziland", "jpn": "スワジランド", "nld": "Swaziland", "por": "Suazilândia", "rus": "Свазиленд", "spa": "Suazilandia", "svk": "Svazijsko", "fin": "Swazimaa", "zho": "斯威士兰", "isr": "סווזילנד"}}, "SE": {"currency": "SEK", "callingCode": "46", "flag": "flag-se", "name": {"common": "Sweden", "deu": "Schweden", "fra": "Suède", "hrv": "Švedska", "ita": "Svezia", "jpn": "スウェーデン", "nld": "Zweden", "por": "Suécia", "rus": "Швеция", "spa": "Suecia", "svk": "šveédsko", "fin": "Ruotsi", "zho": "瑞典", "isr": "שוודיה"}}, "CH": {"currency": "CHE", "callingCode": "41", "flag": "flag-ch", "name": {"common": "Switzerland", "deu": "Schweiz", "fra": "Suisse", "hrv": "Švicarska", "ita": "Svizzera", "jpn": "スイス", "nld": "Zwitserland", "por": "Suíça", "rus": "Швейцария", "spa": "Suiza", "svk": "Švajčiarsko", "fin": "Sveitsi", "zho": "瑞士", "isr": "שווייץ"}}, "SY": {"currency": "SYP", "callingCode": "963", "flag": "flag-sy", "name": {"common": "Syria", "deu": "Syrien", "fra": "Syrie", "hrv": "Sirija", "ita": "Siria", "jpn": "シリア・アラブ共和国", "nld": "Syrië", "por": "Síria", "rus": "Сирия", "spa": "Siria", "svk": "Sýria", "fin": "Syyria", "zho": "叙利亚", "isr": "סוריה"}}, "ST": {"currency": "STD", "callingCode": "239", "flag": "flag-st", "name": {"common": "São Tomé and Príncipe", "deu": "São Tomé und Príncipe", "fra": "São Tomé et Príncipe", "hrv": "Sveti Toma i Princip", "ita": "São Tomé e Príncipe", "jpn": "サントメ・プリンシペ", "nld": "Sao Tomé en Principe", "por": "São Tomé e Príncipe", "rus": "Сан-Томе и Принсипи", "spa": "Santo Tomé y Príncipe", "svk": "Svätý Tomáš a Princov ostrov", "fin": "São Téme ja Príncipe", "zho": "圣多美和普林西比", "isr": "סאו טומה ופרינסיפה"}}, "TW": {"currency": "TWD", "callingCode": "886", "flag": "flag-tw", "name": {"common": "Taiwan", "deu": "Taiwan", "fra": "Taïwan", "hrv": "Tajvan", "ita": "Taiwan", "jpn": "台湾(台湾省/中華民国)", "nld": "Taiwan", "por": "Ilha Formosa", "rus": "Тайвань", "spa": "Taiwán", "svk": "Taiwan", "fin": "Taiwan", "isr": "טייוואן"}}, "TJ": {"currency": "TJS", "callingCode": "992", "flag": "flag-tj", "name": {"common": "Tajikistan", "deu": "Tadschikistan", "fra": "Tadjikistan", "hrv": "Tađikistan", "ita": "Tagikistan", "jpn": "タジキスタン", "nld": "Tadzjikistan", "por": "Tajiquistão", "rus": "Таджикистан", "spa": "Tayikistán", "svk": "Tadžikistan", "fin": "Tadžikistan", "zho": "塔吉克斯坦", "isr": "טג׳יקיסטן"}}, "TZ": {"currency": "TZS", "callingCode": "255", "flag": "flag-tz", "name": {"common": "Tanzania", "deu": "Tansania", "fra": "Tanzanie", "hrv": "Tanzanija", "ita": "Tanzania", "jpn": "タンザニア", "nld": "Tanzania", "por": "Tanzânia", "rus": "Танзания", "spa": "Tanzania", "svk": "Tanzánia", "fin": "Tansania", "zho": "坦桑尼亚", "isr": "טנזניה"}}, "TH": {"currency": "THB", "callingCode": "66", "flag": "flag-th", "name": {"common": "Thailand", "deu": "Thailand", "fra": "Thaïlande", "hrv": "Tajland", "ita": "Tailandia", "jpn": "タイ", "nld": "Thailand", "por": "Tailândia", "rus": "Таиланд", "spa": "Tailandia", "svk": "Thajsko", "fin": "Thaimaa", "zho": "泰国", "isr": "תאילנד"}}, "TL": {"currency": "USD", "callingCode": "670", "flag": "flag-tl", "name": {"common": "Timor-Leste", "deu": "Timor-Leste", "fra": "Timor oriental", "hrv": "Istočni Timor", "ita": "Timor Est", "jpn": "東ティモール", "nld": "Oost-Timor", "por": "Timor-Leste", "rus": "Восточный Тимор", "spa": "Timor Oriental", "svk": "Východný Timor", "fin": "Itä-Timor", "zho": "东帝汶", "isr": "טימור לסטה"}}, "TG": {"currency": "XOF", "callingCode": "228", "flag": "flag-tg", "name": {"common": "Togo", "deu": "Togo", "fra": "Togo", "hrv": "Togo", "ita": "Togo", "jpn": "トーゴ", "nld": "Togo", "por": "Togo", "rus": "Того", "spa": "Togo", "svk": "Togo", "fin": "Togo", "zho": "多哥", "isr": "טוגו"}}, "TK": {"currency": "NZD", "callingCode": "690", "flag": "flag-tk", "name": {"common": "Tokelau", "deu": "Tokelau", "fra": "Tokelau", "hrv": "Tokelau", "ita": "Isole Tokelau", "jpn": "トケラウ", "nld": "Tokelau", "por": "Tokelau", "rus": "Токелау", "spa": "Islas Tokelau", "svk": "Tokelau", "fin": "Tokelau", "zho": "托克劳", "isr": "טוקלאו"}}, "TO": {"currency": "TOP", "callingCode": "676", "flag": "flag-to", "name": {"common": "Tonga", "deu": "Tonga", "fra": "Tonga", "hrv": "Tonga", "ita": "Tonga", "jpn": "トンガ", "nld": "Tonga", "por": "Tonga", "rus": "Тонга", "spa": "Tonga", "svk": "Tonga", "fin": "Tonga", "zho": "汤加", "isr": "טונגה"}}, "TT": {"currency": "TTD", "callingCode": "1868", "flag": "flag-tt", "name": {"common": "Trinidad and Tobago", "deu": "Trinidad und Tobago", "fra": "Trinité-et-Tobago", "hrv": "Trinidad i Tobago", "ita": "Trinidad e Tobago", "jpn": "トリニダード・トバゴ", "nld": "Trinidad en Tobago", "por": "Trinidade e Tobago", "rus": "Тринидад и Тобаго", "spa": "Trinidad y Tobago", "svk": "Trinidad a Tobago", "fin": "Trinidad ja Tobago", "zho": "特立尼达和多巴哥", "isr": "טרינידד וטובגו"}}, "TN": {"currency": "TND", "callingCode": "216", "flag": "flag-tn", "name": {"common": "Tunisia", "deu": "Tunesien", "fra": "Tunisie", "hrv": "Tunis", "ita": "Tunisia", "jpn": "チュニジア", "nld": "Tunesië", "por": "Tunísia", "rus": "Тунис", "spa": "Túnez", "svk": "Tunisko", "fin": "Tunisia", "zho": "突尼斯", "isr": "טוניסיה"}}, "TR": {"currency": "TRY", "callingCode": "90", "flag": "flag-tr", "name": {"common": "Turkey", "deu": "Türkei", "fra": "Turquie", "hrv": "Turska", "ita": "Turchia", "jpn": "トルコ", "nld": "Turkije", "por": "Turquia", "rus": "Турция", "spa": "Turquía", "svk": "Turecko", "fin": "Turkki", "zho": "土耳其", "isr": "טורקיה"}}, "TM": {"currency": "TMT", "callingCode": "993", "flag": "flag-tm", "name": {"common": "Turkmenistan", "deu": "Turkmenistan", "fra": "Turkménistan", "hrv": "Turkmenistan", "ita": "Turkmenistan", "jpn": "トルクメニスタン", "nld": "Turkmenistan", "por": "Turquemenistão", "rus": "Туркмения", "spa": "Turkmenistán", "svk": "Turkménsko", "fin": "Turkmenistan", "zho": "土库曼斯坦", "isr": "טורקמניסטן"}}, "TC": {"currency": "USD", "callingCode": "1649", "flag": "flag-tc", "name": {"common": "Turks and Caicos Islands", "deu": "Turks-und Caicosinseln", "fra": "Îles Turques-et-Caïques", "hrv": "Otoci Turks i Caicos", "ita": "Isole Turks e Caicos", "jpn": "タークス・カイコス諸島", "nld": "Turks-en Caicoseilanden", "por": "Ilhas Turks e Caicos", "rus": "Теркс и Кайкос", "spa": "Islas Turks y Caicos", "svk": "Turks a Caicos", "fin": "Turks-ja Caicossaaret", "zho": "特克斯和凯科斯群岛", "isr": "איי טורקס וקאיקוס"}}, "TV": {"currency": "AUD", "callingCode": "688", "flag": "flag-tv", "name": {"common": "Tuvalu", "deu": "Tuvalu", "fra": "Tuvalu", "hrv": "Tuvalu", "ita": "Tuvalu", "jpn": "ツバル", "nld": "Tuvalu", "por": "Tuvalu", "rus": "Тувалу", "spa": "Tuvalu", "svk": "Tuvalu", "fin": "Tuvalu", "zho": "图瓦卢", "isr": "טובאלו"}}, "UG": {"currency": "UGX", "callingCode": "256", "flag": "flag-ug", "name": {"common": "Uganda", "deu": "Uganda", "fra": "Ouganda", "hrv": "Uganda", "ita": "Uganda", "jpn": "ウガンダ", "nld": "Oeganda", "por": "Uganda", "rus": "Уганда", "spa": "Uganda", "svk": "Uganda", "fin": "Uganda", "zho": "乌干达", "isr": "אוגנדה"}}, "UA": {"currency": "UAH", "callingCode": "380", "flag": "flag-ua", "name": {"common": "Ukraine", "deu": "Ukraine", "fra": "Ukraine", "hrv": "Ukrajina", "ita": "Ucraina", "jpn": "ウクライナ", "nld": "Oekraïne", "por": "Ucrânia", "rus": "Украина", "spa": "Ucrania", "svk": "Ukrajina", "fin": "Ukraina", "zho": "乌克兰", "isr": "אוקראינה"}}, "AE": {"currency": "AED", "callingCode": "971", "flag": "flag-ae", "name": {"common": "United Arab Emirates", "deu": "Vereinigte Arabische Emirate", "fra": "Émirats arabes unis", "hrv": "Ujedinjeni Arapski Emirati", "ita": "Emirati Arabi Uniti", "jpn": "アラブ首長国連邦", "nld": "Verenigde Arabische Emiraten", "por": "Emirados Árabes Unidos", "rus": "Объединённые Арабские Эмираты", "spa": "Emiratos Árabes Unidos", "svk": "Spojené arabské emiráty", "fin": "Arabiemiraatit", "zho": "阿拉伯联合酋长国", "isr": "איחוד האמירויות הערביות"}}, "GB": {"currency": "GBP", "callingCode": "44", "flag": "flag-gb", "name": {"common": "United Kingdom", "deu": "Vereinigtes Königreich", "fra": "Royaume-Uni", "hrv": "Ujedinjeno Kraljevstvo", "ita": "Regno Unito", "jpn": "イギリス", "nld": "Verenigd Koninkrijk", "por": "Reino Unido", "rus": "Великобритания", "spa": "Reino Unido", "svk": "Veľká Británia (Spojené kráľovstvo)", "fin": "Yhdistynyt kuningaskunta", "zho": "英国", "isr": "הממלכה המאוחדת"}}, "US": {"currency": "USD", "callingCode": "1", "flag": "flag-us", "name": {"common": "United States", "deu": "Vereinigte Staaten von Amerika", "fra": "États-Unis", "hrv": "Sjedinjene Američke Države", "ita": "Stati Uniti d'America", "jpn": "アメリカ合衆国", "nld": "Verenigde Staten", "por": "Estados Unidos", "rus": "Соединённые Штаты Америки", "spa": "Estados Unidos", "svk": "Spojené štáty", "fin": "Yhdysvallat", "zho": "美国", "isr": "ארצות הברית"}}, "UM": {"currency": "USD", "flag": "flag-um", "name": {"common": "United States Minor Outlying Islands", "deu": "Kleinere Inselbesitzungen der Vereinigten Staaten", "fra": "Îles mineures éloignées des États-Unis", "hrv": "Mali udaljeni otoci SAD-a", "ita": "Isole minori esterne degli Stati Uniti d'America", "jpn": "合衆国領有小離島", "nld": "Kleine afgelegen eilanden van de Verenigde Staten", "por": "Ilhas Menores Distantes dos Estados Unidos", "rus": "Внешние малые острова США", "spa": "Islas Ultramarinas Menores de Estados Unidos", "svk": "Menšie odľahlé ostrovy USA", "fin": "Yhdysvaltain asumattomat saaret", "zho": "美国本土外小岛屿", "isr": "האיים המרוחקים הקטנים של ארה״ב"}}, "VI": {"currency": "USD", "callingCode": "1340", "flag": "flag-vi", "name": {"common": "United States Virgin Islands", "deu": "Amerikanische Jungferninseln", "fra": "Îles Vierges des États-Unis", "hrv": "Američki Djevičanski Otoci", "ita": "Isole Vergini americane", "jpn": "アメリカ領ヴァージン諸島", "nld": "Amerikaanse Maagdeneilanden", "por": "Ilhas Virgens dos Estados Unidos", "rus": "Виргинские Острова", "spa": "Islas Vírgenes de los Estados Unidos", "svk": "Americké Panenské ostrovy", "fin": "Neitsytsaaret", "zho": "美属维尔京群岛", "isr": "איי הבתולה של ארצות הברית"}}, "UY": {"currency": "UYI", "callingCode": "598", "flag": "flag-uy", "name": {"common": "Uruguay", "deu": "Uruguay", "fra": "Uruguay", "hrv": "Urugvaj", "ita": "Uruguay", "jpn": "ウルグアイ", "nld": "Uruguay", "por": "Uruguai", "rus": "Уругвай", "spa": "Uruguay", "svk": "Uruguaj", "fin": "Uruguay", "zho": "乌拉圭", "isr": "אורוגוואי"}}, "UZ": {"currency": "UZS", "callingCode": "998", "flag": "flag-uz", "name": {"common": "Uzbekistan", "deu": "Usbekistan", "fra": "Ouzbékistan", "hrv": "Uzbekistan", "ita": "Uzbekistan", "jpn": "ウズベキスタン", "nld": "Oezbekistan", "por": "Uzbequistão", "rus": "Узбекистан", "spa": "Uzbekistán", "svk": "Uzbekistan", "fin": "Uzbekistan", "zho": "乌兹别克斯坦", "isr": "אוזבקיסטן"}}, "VU": {"currency": "VUV", "callingCode": "678", "flag": "flag-vu", "name": {"common": "Vanuatu", "deu": "Vanuatu", "fra": "Vanuatu", "hrv": "Vanuatu", "ita": "Vanuatu", "jpn": "バヌアツ", "nld": "Vanuatu", "por": "Vanuatu", "rus": "Вануату", "spa": "Vanuatu", "svk": "Vanuatu", "fin": "Vanuatu", "zho": "瓦努阿图", "isr": "ונואטו"}}, "VA": {"currency": "EUR", "callingCode": "3906698", "flag": "flag-va", "name": {"common": "Vatican City", "deu": "Vatikanstadt", "fra": "Cité du Vatican", "hrv": "Vatikan", "ita": "Città del Vaticano", "jpn": "バチカン市国", "nld": "Vaticaanstad", "por": "Cidade do Vaticano", "rus": "Ватикан", "spa": "Ciudad del Vaticano", "svk": "Vatikán", "fin": "Vatikaani", "zho": "梵蒂冈", "isr": "הוותיקן"}}, "VE": {"currency": "VEF", "callingCode": "58", "flag": "flag-ve", "name": {"common": "Venezuela", "deu": "Venezuela", "fra": "Venezuela", "hrv": "Venezuela", "ita": "Venezuela", "jpn": "ベネズエラ・ボリバル共和国", "nld": "Venezuela", "por": "Venezuela", "rus": "Венесуэла", "spa": "Venezuela", "svk": "Venezuela", "fin": "Venezuela", "zho": "委内瑞拉", "isr": "ונצואלה"}}, "VN": {"currency": "VND", "callingCode": "84", "flag": "flag-vn", "name": {"common": "Vietnam", "deu": "Vietnam", "fra": "Viêt Nam", "hrv": "Vijetnam", "ita": "Vietnam", "jpn": "ベトナム", "nld": "Vietnam", "por": "Vietname", "rus": "Вьетнам", "spa": "Vietnam", "svk": "Vietnam", "fin": "Vietnam", "zho": "越南", "isr": "וייטנאם"}}, "WF": {"currency": "XPF", "callingCode": "681", "flag": "flag-wf", "name": {"common": "Wallis and Futuna", "deu": "Wallis und Futuna", "fra": "Wallis-et-Futuna", "hrv": "Wallis i Fortuna", "ita": "Wallis e Futuna", "jpn": "ウォリス・フツナ", "nld": "Wallis en Futuna", "por": "Wallis e Futuna", "rus": "Уоллис и Футуна", "spa": "Wallis y Futuna", "svk": "Wallis a Futuna", "fin": "Wallis ja Futuna", "zho": "瓦利斯和富图纳群岛", "isr": "איי ווליס ופוטונה"}}, "EH": {"currency": "MAD", "callingCode": "212", "flag": "flag-eh", "name": {"common": "Western Sahara", "deu": "Westsahara", "fra": "Sahara Occidental", "hrv": "Zapadna Sahara", "ita": "Sahara Occidentale", "jpn": "西サハラ", "nld": "Westelijke Sahara", "por": "Saara Ocidental", "rus": "Западная Сахара", "spa": "Sahara Occidental", "svk": "Západná Sahara", "fin": "Länsi-Sahara", "zho": "西撒哈拉", "isr": "סהרה המערבית"}}, "YE": {"currency": "YER", "callingCode": "967", "flag": "flag-ye", "name": {"common": "Yemen", "deu": "Jemen", "fra": "Yémen", "hrv": "Jemen", "ita": "Yemen", "jpn": "イエメン", "nld": "Jemen", "por": "Iémen", "rus": "Йемен", "spa": "Yemen", "svk": "Jemen", "fin": "Jemen", "zho": "也门", "isr": "תימן"}}, "ZM": {"currency": "ZMW", "callingCode": "260", "flag": "flag-zm", "name": {"common": "Zambia", "deu": "Sambia", "fra": "Zambie", "hrv": "Zambija", "ita": "Zambia", "jpn": "ザンビア", "nld": "Zambia", "por": "Zâmbia", "rus": "Замбия", "spa": "Zambia", "svk": "Zambia", "fin": "Sambia", "zho": "赞比亚", "isr": "זמביה"}}, "ZW": {"currency": "ZWL", "callingCode": "263", "flag": "flag-zw", "name": {"common": "Zimbabwe", "deu": "Simbabwe", "fra": "Zimbabwe", "hrv": "Zimbabve", "ita": "Zimbabwe", "jpn": "ジンバブエ", "nld": "Zimbabwe", "por": "Zimbabwe", "rus": "Зимбабве", "spa": "Zimbabue", "svk": "Zimbabwe", "fin": "Zimbabwe", "zho": "津巴布韦", "isr": "זימבבואה"}}, "AX": {"currency": "EUR", "callingCode": "358", "flag": "flag-ax", "name": {"common": "Åland Islands", "deu": "Åland", "fra": "Ahvenanmaa", "hrv": "Ålandski otoci", "ita": "Isole Aland", "jpn": "オーランド諸島", "nld": "Ålandeilanden", "por": "Alândia", "rus": "Аландские острова", "spa": "Alandia", "svk": "Alandy", "fin": "Ahvenanmaa", "zho": "奥兰群岛", "isr": "איי אולנד"}}} \ No newline at end of file diff --git a/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_lib_assets_data_countriesemoji.json b/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_lib_assets_data_countriesemoji.json deleted file mode 100644 index dcea665e..00000000 --- a/app/src/main/res/raw/node_modules_reactnativecountrypickermodal_lib_assets_data_countriesemoji.json +++ /dev/null @@ -1 +0,0 @@ -{"AF":{"currency":["AFN"],"callingCode":["93"],"region":"Asia","subregion":"Southern Asia","flag":"flag-af","name":{"common":"Afghanistan","ces":"Afghánistán","cym":"Affganistan","deu":"Afghanistan","fra":"Afghanistan","hrv":"Afganistan","ita":"Afghanistan","jpn":"アフガニスタン","nld":"Afghanistan","por":"Afeganistão","rus":"Афганистан","slk":"Afganistan","spa":"Afganistán","fin":"Afganistan","est":"Afganistan","zho":"阿富汗","pol":"Afganistan","urd":"افغانستان","kor":"아프가니스탄"}},"AL":{"currency":["ALL"],"callingCode":["355"],"region":"Europe","subregion":"Southern Europe","flag":"flag-al","name":{"common":"Albania","ces":"Albánie","cym":"Albania","deu":"Albanien","fra":"Albanie","hrv":"Albanija","ita":"Albania","jpn":"アルバニア","nld":"Albanië","por":"Albânia","rus":"Албания","slk":"Albánsko","spa":"Albania","fin":"Albania","est":"Albaania","zho":"阿尔巴尼亚","pol":"Albania","urd":"البانیا","kor":"알바니아"}},"DZ":{"currency":["DZD"],"callingCode":["213"],"region":"Africa","subregion":"Northern Africa","flag":"flag-dz","name":{"common":"Algeria","ces":"Alžírsko","cym":"Algeria","deu":"Algerien","fra":"Algérie","hrv":"Alžir","ita":"Algeria","jpn":"アルジェリア","nld":"Algerije","por":"Argélia","rus":"Алжир","slk":"Alžírsko","spa":"Argelia","fin":"Algeria","est":"Alžeeria","zho":"阿尔及利亚","pol":"Algieria","urd":"الجزائر","kor":"알제리"}},"AS":{"currency":["USD"],"callingCode":["1684"],"region":"Oceania","subregion":"Polynesia","flag":"flag-as","name":{"common":"American Samoa","ces":"Americká Samoa","deu":"Amerikanisch-Samoa","fra":"Samoa américaines","hrv":"Američka Samoa","ita":"Samoa Americane","jpn":"アメリカ領サモア","nld":"Amerikaans Samoa","por":"Samoa Americana","rus":"Американское Самоа","slk":"Americká Samoa","spa":"Samoa Americana","fin":"Amerikan Samoa","est":"Ameerika Samoa","zho":"美属萨摩亚","pol":"Samoa Amerykańskie","urd":"امریکی سمووا","kor":"아메리칸사모아"}},"AD":{"currency":["EUR"],"callingCode":["376"],"region":"Europe","subregion":"Southern Europe","flag":"flag-ad","name":{"common":"Andorra","ces":"Andorra","cym":"Andorra","deu":"Andorra","fra":"Andorre","hrv":"Andora","ita":"Andorra","jpn":"アンドラ","nld":"Andorra","por":"Andorra","rus":"Андорра","slk":"Andorra","spa":"Andorra","fin":"Andorra","est":"Andorra","zho":"安道尔","pol":"Andora","urd":"انڈورا","kor":"안도라"}},"AO":{"currency":["AOA"],"callingCode":["244"],"region":"Africa","subregion":"Middle Africa","flag":"flag-ao","name":{"common":"Angola","ces":"Angola","cym":"Angola","deu":"Angola","fra":"Angola","hrv":"Angola","ita":"Angola","jpn":"アンゴラ","nld":"Angola","por":"Angola","rus":"Ангола","slk":"Angola","spa":"Angola","fin":"Angola","est":"Angola","zho":"安哥拉","pol":"Angola","urd":"انگولہ","kor":"앙골라"}},"AI":{"currency":["XCD"],"callingCode":["1264"],"region":"Americas","subregion":"Caribbean","flag":"flag-ai","name":{"common":"Anguilla","ces":"Anguilla","deu":"Anguilla","fra":"Anguilla","hrv":"Angvila","ita":"Anguilla","jpn":"アンギラ","nld":"Anguilla","por":"Anguilla","rus":"Ангилья","slk":"Anguilla","spa":"Anguilla","fin":"Anguilla","est":"Anguilla","zho":"安圭拉","pol":"Anguilla","urd":"اینگویلا","kor":"앵귈라"}},"AQ":{"currency":[],"callingCode":[],"region":"Antarctic","subregion":"","flag":"flag-aq","name":{"common":"Antarctica","ces":"Antarktida","cym":"Yr Antarctig","deu":"Antarktis","fra":"Antarctique","hrv":"Antarktika","ita":"Antartide","jpn":"南極","nld":"Antarctica","por":"Antártida","rus":"Антарктида","slk":"Antarktída","spa":"Antártida","fin":"Etelämanner","est":"Antarktika","zho":"南极洲","pol":"Antarktyka","urd":"انٹارکٹکا","kor":"남극"}},"AG":{"currency":["XCD"],"callingCode":["1268"],"region":"Americas","subregion":"Caribbean","flag":"flag-ag","name":{"common":"Antigua and Barbuda","ces":"Antigua a Barbuda","cym":"Antigwa a Barbiwda","deu":"Antigua und Barbuda","fra":"Antigua-et-Barbuda","hrv":"Antigva i Barbuda","ita":"Antigua e Barbuda","jpn":"アンティグア・バーブーダ","nld":"Antigua en Barbuda","por":"Antígua e Barbuda","rus":"Антигуа и Барбуда","slk":"Antigua a Barbuda","spa":"Antigua y Barbuda","fin":"Antigua ja Barbuda","est":"Antigua ja Barbuda","zho":"安提瓜和巴布达","pol":"Antigua i Barbuda","urd":"اینٹیگوا و باربوڈا","kor":"앤티가 바부다"}},"AR":{"currency":["ARS"],"callingCode":["54"],"region":"Americas","subregion":"South America","flag":"flag-ar","name":{"common":"Argentina","ces":"Argentina","cym":"Ariannin","deu":"Argentinien","fra":"Argentine","hrv":"Argentina","ita":"Argentina","jpn":"アルゼンチン","nld":"Argentinië","por":"Argentina","rus":"Аргентина","slk":"Argentína","spa":"Argentina","fin":"Argentiina","est":"Argentina","zho":"阿根廷","pol":"Argentyna","urd":"ارجنٹائن","kor":"아르헨티나"}},"AM":{"currency":["AMD"],"callingCode":["374"],"region":"Asia","subregion":"Western Asia","flag":"flag-am","name":{"common":"Armenia","ces":"Arménie","cym":"Armenia","deu":"Armenien","fra":"Arménie","hrv":"Armenija","ita":"Armenia","jpn":"アルメニア","nld":"Armenië","por":"Arménia","rus":"Армения","slk":"Arménsko","spa":"Armenia","fin":"Armenia","est":"Armeenia","zho":"亚美尼亚","pol":"Armenia","urd":"آرمینیا","kor":"아르메니아"}},"AW":{"currency":["AWG"],"callingCode":["297"],"region":"Americas","subregion":"Caribbean","flag":"flag-aw","name":{"common":"Aruba","ces":"Aruba","deu":"Aruba","fra":"Aruba","hrv":"Aruba","ita":"Aruba","jpn":"アルバ","nld":"Aruba","por":"Aruba","rus":"Аруба","slk":"Aruba","spa":"Aruba","fin":"Aruba","est":"Aruba","zho":"阿鲁巴","pol":"Aruba","urd":"اروبا","kor":"아루바"}},"AU":{"currency":["AUD"],"callingCode":["61"],"region":"Oceania","subregion":"Australia and New Zealand","flag":"flag-au","name":{"common":"Australia","ces":"Austrálie","cym":"Awstralia","deu":"Australien","fra":"Australie","hrv":"Australija","ita":"Australia","jpn":"オーストラリア","nld":"Australië","por":"Austrália","rus":"Австралия","slk":"Austrália","spa":"Australia","fin":"Australia","est":"Austraalia","zho":"澳大利亚","pol":"Australia","urd":"آسٹریلیا","kor":"호주"}},"AT":{"currency":["EUR"],"callingCode":["43"],"region":"Europe","subregion":"Western Europe","flag":"flag-at","name":{"common":"Austria","ces":"Rakousko","cym":"Awstria","deu":"Österreich","fra":"Autriche","hrv":"Austrija","ita":"Austria","jpn":"オーストリア","nld":"Oostenrijk","por":"Áustria","rus":"Австрия","slk":"Rakúsko","spa":"Austria","fin":"Itävalta","est":"Austria","zho":"奥地利","pol":"Austria","urd":"آسٹریا","kor":"오스트리아"}},"AZ":{"currency":["AZN"],"callingCode":["994"],"region":"Asia","subregion":"Western Asia","flag":"flag-az","name":{"common":"Azerbaijan","ces":"Ázerbájdžán","cym":"Aserbaijan","deu":"Aserbaidschan","fra":"Azerbaïdjan","hrv":"Azerbajdžan","ita":"Azerbaijan","jpn":"アゼルバイジャン","nld":"Azerbeidzjan","por":"Azerbeijão","rus":"Азербайджан","slk":"AzerbajLJan","spa":"Azerbaiyán","fin":"Azerbaidzan","est":"Aserbaidžaan","zho":"阿塞拜疆","pol":"Azerbejdżan","urd":"آذربائیجان","kor":"아제르바이잔"}},"BS":{"currency":["BSD"],"callingCode":["1242"],"region":"Americas","subregion":"Caribbean","flag":"flag-bs","name":{"common":"Bahamas","ces":"Bahamy","cym":"Bahamas","deu":"Bahamas","fra":"Bahamas","hrv":"Bahami","ita":"Bahamas","jpn":"バハマ","nld":"Bahama’s","por":"Bahamas","rus":"Багамские Острова","slk":"Bahamy","spa":"Bahamas","fin":"Bahamasaaret","est":"Bahama","zho":"巴哈马","pol":"Bahamy","urd":"بہاماس","kor":"바하마"}},"BH":{"currency":["BHD"],"callingCode":["973"],"region":"Asia","subregion":"Western Asia","flag":"flag-bh","name":{"common":"Bahrain","ces":"Bahrajn","cym":"Bahrain","deu":"Bahrain","fra":"Bahreïn","hrv":"Bahrein","ita":"Bahrein","jpn":"バーレーン","nld":"Bahrein","por":"Bahrein","rus":"Бахрейн","slk":"Bahrajn","spa":"Bahrein","fin":"Bahrain","est":"Bahrein","zho":"巴林","pol":"Bahrajn","urd":"بحرین","kor":"바레인"}},"BD":{"currency":["BDT"],"callingCode":["880"],"region":"Asia","subregion":"Southern Asia","flag":"flag-bd","name":{"common":"Bangladesh","ces":"Bangladéš","cym":"Bangladesh","deu":"Bangladesch","fra":"Bangladesh","hrv":"Bangladeš","ita":"Bangladesh","jpn":"バングラデシュ","nld":"Bangladesh","por":"Bangladesh","rus":"Бангладеш","slk":"Bangladéš","spa":"Bangladesh","fin":"Bangladesh","est":"Bangladesh","zho":"孟加拉国","pol":"Bangladesz","urd":"بنگلہ دیش","kor":"방글라데시"}},"BB":{"currency":["BBD"],"callingCode":["1246"],"region":"Americas","subregion":"Caribbean","flag":"flag-bb","name":{"common":"Barbados","ces":"Barbados","cym":"Barbados","deu":"Barbados","fra":"Barbade","hrv":"Barbados","ita":"Barbados","jpn":"バルバドス","nld":"Barbados","por":"Barbados","rus":"Барбадос","slk":"Barbados","spa":"Barbados","fin":"Barbados","est":"Barbados","zho":"巴巴多斯","pol":"Barbados","urd":"بارباڈوس","kor":"바베이도스"}},"BY":{"currency":["BYN"],"callingCode":["375"],"region":"Europe","subregion":"Eastern Europe","flag":"flag-by","name":{"common":"Belarus","ces":"Bělorusko","cym":"Belarws","deu":"Weißrussland","fra":"Biélorussie","hrv":"Bjelorusija","ita":"Bielorussia","jpn":"ベラルーシ","nld":"Wit-Rusland","por":"Bielorússia","rus":"Беларусь","slk":"Bielorusko","spa":"Bielorrusia","fin":"Valko-Venäjä","est":"Valgevene","zho":"白俄罗斯","pol":"Białoruś","urd":"بیلاروس","kor":"벨라루스"}},"BE":{"currency":["EUR"],"callingCode":["32"],"region":"Europe","subregion":"Western Europe","flag":"flag-be","name":{"common":"Belgium","ces":"Belgie","cym":"Gwlad Belg","deu":"Belgien","fra":"Belgique","hrv":"Belgija","ita":"Belgio","jpn":"ベルギー","nld":"België","por":"Bélgica","rus":"Бельгия","slk":"Belgicko","spa":"Bélgica","fin":"Belgia","est":"Belgia","zho":"比利时","pol":"Belgia","urd":"بلجئیم","kor":"벨기에"}},"BZ":{"currency":["BZD"],"callingCode":["501"],"region":"Americas","subregion":"Central America","flag":"flag-bz","name":{"common":"Belize","ces":"Belize","cym":"Belîs","deu":"Belize","fra":"Belize","hrv":"Belize","ita":"Belize","jpn":"ベリーズ","nld":"Belize","por":"Belize","rus":"Белиз","slk":"Belize","spa":"Belice","fin":"Belize","est":"Belize","zho":"伯利兹","pol":"Belize","urd":"بیلیز","kor":"벨리즈"}},"BJ":{"currency":["XOF"],"callingCode":["229"],"region":"Africa","subregion":"Western Africa","flag":"flag-bj","name":{"common":"Benin","ces":"Benin","cym":"Benin","deu":"Benin","fra":"Bénin","hrv":"Benin","ita":"Benin","jpn":"ベナン","nld":"Benin","por":"Benin","rus":"Бенин","slk":"Benin","spa":"Benín","fin":"Benin","est":"Benin","zho":"贝宁","pol":"Benin","urd":"بینن","kor":"베냉"}},"BM":{"currency":["BMD"],"callingCode":["1441"],"region":"Americas","subregion":"North America","flag":"flag-bm","name":{"common":"Bermuda","ces":"Bermudy","cym":"Bermiwda","deu":"Bermuda","fra":"Bermudes","hrv":"Bermudi","ita":"Bermuda","jpn":"バミューダ","nld":"Bermuda","por":"Bermudas","rus":"Бермудские Острова","slk":"Bermudy","spa":"Bermudas","fin":"Bermuda","est":"Bermuda","zho":"百慕大","pol":"Bermudy","urd":"برمودا","kor":"버뮤다"}},"BT":{"currency":["BTN","INR"],"callingCode":["975"],"region":"Asia","subregion":"Southern Asia","flag":"flag-bt","name":{"common":"Bhutan","ces":"Bhútán","cym":"Bhwtan","deu":"Bhutan","fra":"Bhoutan","hrv":"Butan","ita":"Bhutan","jpn":"ブータン","nld":"Bhutan","por":"Butão","rus":"Бутан","slk":"Bhután","spa":"Bután","fin":"Bhutan","est":"Bhutan","zho":"不丹","pol":"Bhutan","urd":"بھوٹان","kor":"부탄"}},"BO":{"currency":["BOB"],"callingCode":["591"],"region":"Americas","subregion":"South America","flag":"flag-bo","name":{"common":"Bolivia","ces":"Bolívie","cym":"Bolifia","deu":"Bolivien","fra":"Bolivie","hrv":"Bolivija","ita":"Bolivia","jpn":"ボリビア多民族国","nld":"Bolivia","por":"Bolívia","rus":"Боливия","slk":"Bolívia","spa":"Bolivia","fin":"Bolivia","est":"Boliivia","zho":"玻利维亚","pol":"Boliwia","urd":"بولیویا","kor":"볼리비아"}},"BA":{"currency":["BAM"],"callingCode":["387"],"region":"Europe","subregion":"Southern Europe","flag":"flag-ba","name":{"common":"Bosnia and Herzegovina","ces":"Bosna a Hercegovina","cym":"Bosnia a Hercegovina","deu":"Bosnien und Herzegowina","fra":"Bosnie-Herzégovine","hrv":"Bosna i Hercegovina","ita":"Bosnia ed Erzegovina","jpn":"ボスニア・ヘルツェゴビナ","nld":"Bosnië en Herzegovina","por":"Bósnia e Herzegovina","rus":"Босния и Герцеговина","slk":"Bosna a Hercegovina","spa":"Bosnia y Herzegovina","fin":"Bosnia ja Hertsegovina","est":"Bosnia ja Hertsegoviina","zho":"波斯尼亚和黑塞哥维那","pol":"Bośnia i Hercegowina","urd":"بوسنیا و ہرزیگووینا","kor":"보스니아 헤르체고비나"}},"BW":{"currency":["BWP"],"callingCode":["267"],"region":"Africa","subregion":"Southern Africa","flag":"flag-bw","name":{"common":"Botswana","ces":"Botswana","deu":"Botswana","fra":"Botswana","hrv":"Bocvana","ita":"Botswana","jpn":"ボツワナ","nld":"Botswana","por":"Botswana","rus":"Ботсвана","slk":"Botswana","spa":"Botswana","fin":"Botswana","est":"Botswana","zho":"博茨瓦纳","pol":"Botswana","urd":"بوٹسوانا","kor":"보츠와나"}},"BV":{"currency":["NOK"],"callingCode":[],"region":"Antarctic","subregion":"","flag":"flag-bv","name":{"common":"Bouvet Island","ces":"Bouvetův ostrov","deu":"Bouvetinsel","fra":"Île Bouvet","hrv":"Otok Bouvet","ita":"Isola Bouvet","jpn":"ブーベ島","nld":"Bouveteiland","por":"Ilha Bouvet","rus":"Остров Буве","slk":"Bouvetov ostrov","spa":"Isla Bouvet","fin":"Bouvet'nsaari","est":"Bouvet’ saar","zho":"布维岛","pol":"Wyspa Bouveta","urd":"جزیرہ بووہ","kor":"부베 섬"}},"BR":{"currency":["BRL"],"callingCode":["55"],"region":"Americas","subregion":"South America","flag":"flag-br","name":{"common":"Brazil","ces":"Brazílie","cym":"Brasil","deu":"Brasilien","fra":"Brésil","hrv":"Brazil","ita":"Brasile","jpn":"ブラジル","nld":"Brazilië","por":"Brasil","rus":"Бразилия","slk":"Brazília","spa":"Brasil","fin":"Brasilia","est":"Brasiilia","zho":"巴西","pol":"Brazylia","urd":"برازیل","kor":"브라질"}},"IO":{"currency":["USD"],"callingCode":["246"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-io","name":{"common":"British Indian Ocean Territory","ces":"Britské indickooceánské území","cym":"Tiriogaeth Brydeinig Cefnfor India","deu":"Britisches Territorium im Indischen Ozean","fra":"Territoire britannique de l'océan Indien","hrv":"Britanski Indijskooceanski teritorij","ita":"Territorio britannico dell'oceano indiano","jpn":"イギリス領インド洋地域","nld":"Britse Gebieden in de Indische Oceaan","por":"Território Britânico do Oceano Índico","rus":"Британская территория в Индийском океане","slk":"Britské indickooceánske územie","spa":"Territorio Británico del Océano Índico","fin":"Brittiläinen Intian valtameren alue","est":"Briti India ookeani ala","zho":"英属印度洋领地","pol":"Brytyjskie Terytorium Oceanu Indyjskiego","urd":"برطانوی بحرہند خطہ","kor":"인도"}},"VG":{"currency":["USD"],"callingCode":["1284"],"region":"Americas","subregion":"Caribbean","flag":"flag-vg","name":{"common":"British Virgin Islands","ces":"Britské Panenské ostrovy","deu":"Britische Jungferninseln","fra":"Îles Vierges britanniques","hrv":"Britanski Djevičanski Otoci","ita":"Isole Vergini Britanniche","jpn":"イギリス領ヴァージン諸島","nld":"Britse Maagdeneilanden","por":"Ilhas Virgens","rus":"Британские Виргинские острова","slk":"Panenské ostrovy","spa":"Islas Vírgenes del Reino Unido","fin":"Neitsytsaaret","est":"Briti Neitsisaared","zho":"英属维尔京群岛","pol":"Brytyjskie Wyspy Dziewicze","urd":"برطانوی جزائر ورجن","kor":"영국령 버진아일랜드"}},"BN":{"currency":["BND"],"callingCode":["673"],"region":"Asia","subregion":"South-Eastern Asia","flag":"flag-bn","name":{"common":"Brunei","ces":"Brunej","cym":"Brunei","deu":"Brunei","fra":"Brunei","hrv":"Brunej","ita":"Brunei","jpn":"ブルネイ・ダルサラーム","nld":"Brunei","por":"Brunei","rus":"Бруней","slk":"Brunej","spa":"Brunei","fin":"Brunei","est":"Brunei","zho":"文莱","pol":"Brunei","urd":"برونائی","kor":"브루나이"}},"BG":{"currency":["BGN"],"callingCode":["359"],"region":"Europe","subregion":"Eastern Europe","flag":"flag-bg","name":{"common":"Bulgaria","ces":"Bulharsko","cym":"Bwlgaria","deu":"Bulgarien","fra":"Bulgarie","hrv":"Bugarska","ita":"Bulgaria","jpn":"ブルガリア","nld":"Bulgarije","por":"Bulgária","rus":"Болгария","slk":"Bulharsko","spa":"Bulgaria","fin":"Bulgaria","est":"Bulgaaria","zho":"保加利亚","pol":"Bułgaria","urd":"بلغاریہ","kor":"불가리아"}},"BF":{"currency":["XOF"],"callingCode":["226"],"region":"Africa","subregion":"Western Africa","flag":"flag-bf","name":{"common":"Burkina Faso","ces":"Burkina Faso","cym":"Bwrcina Ffaso","deu":"Burkina Faso","fra":"Burkina Faso","hrv":"Burkina Faso","ita":"Burkina Faso","jpn":"ブルキナファソ","nld":"Burkina Faso","por":"Burkina Faso","rus":"Буркина-Фасо","slk":"Burkina Faso","spa":"Burkina Faso","fin":"Burkina Faso","est":"Burkina Faso","zho":"布基纳法索","pol":"Burkina Faso","urd":"برکینا فاسو","kor":"부르키나파소"}},"BI":{"currency":["BIF"],"callingCode":["257"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-bi","name":{"common":"Burundi","ces":"Burundi","cym":"Bwrwndi","deu":"Burundi","fra":"Burundi","hrv":"Burundi","ita":"Burundi","jpn":"ブルンジ","nld":"Burundi","por":"Burundi","rus":"Бурунди","slk":"Burundi","spa":"Burundi","fin":"Burundi","est":"Burundi","zho":"布隆迪","pol":"Burundi","urd":"برونڈی","kor":"부룬디"}},"KH":{"currency":["KHR"],"callingCode":["855"],"region":"Asia","subregion":"South-Eastern Asia","flag":"flag-kh","name":{"common":"Cambodia","ces":"Kambodža","cym":"Cambodia","deu":"Kambodscha","fra":"Cambodge","hrv":"Kambodža","ita":"Cambogia","jpn":"カンボジア","nld":"Cambodja","por":"Camboja","rus":"Камбоджа","slk":"Kambodža","spa":"Camboya","fin":"Kambodža","est":"Kambodža","zho":"柬埔寨","pol":"Kambodża","urd":"کمبوڈیا","kor":"캄보디아"}},"CM":{"currency":["XAF"],"callingCode":["237"],"region":"Africa","subregion":"Middle Africa","flag":"flag-cm","name":{"common":"Cameroon","ces":"Kamerun","cym":"Camerŵn","deu":"Kamerun","fra":"Cameroun","hrv":"Kamerun","ita":"Camerun","jpn":"カメルーン","nld":"Kameroen","por":"Camarões","rus":"Камерун","slk":"Kamerun","spa":"Camerún","fin":"Kamerun","est":"Kamerun","zho":"喀麦隆","pol":"WybrzeŻe Kości Słoniowej","urd":"کیمرون","kor":"카메룬"}},"CA":{"currency":["CAD"],"callingCode":["1"],"region":"Americas","subregion":"North America","flag":"flag-ca","name":{"common":"Canada","ces":"Kanada","cym":"Canada","deu":"Kanada","fra":"Canada","hrv":"Kanada","ita":"Canada","jpn":"カナダ","nld":"Canada","por":"Canadá","rus":"Канада","slk":"Kanada","spa":"Canadá","fin":"Kanada","est":"Kanada","zho":"加拿大","pol":"Kanada","urd":"کینیڈا","kor":"캐나다"}},"CV":{"currency":["CVE"],"callingCode":["238"],"region":"Africa","subregion":"Western Africa","flag":"flag-cv","name":{"common":"Cape Verde","ces":"Kapverdy","cym":"Penrhyn Verde","deu":"Kap Verde","fra":"Îles du Cap-Vert","hrv":"Zelenortska Republika","ita":"Capo Verde","jpn":"カーボベルデ","nld":"Kaapverdië","por":"Cabo Verde","rus":"Кабо-Верде","slk":"Kapverdy","spa":"Cabo Verde","fin":"Kap Verde","est":"Roheneemesaared","zho":"佛得角","pol":"Republika Zielonego Przylądka","urd":"کیپ ورڈی","kor":"카보베르데"}},"BQ":{"currency":["USD"],"callingCode":["599"],"region":"Americas","subregion":"Caribbean","flag":"flag-bq","name":{"common":"Caribbean Netherlands","ces":"Karibské Nizozemsko","deu":"Karibische Niederlande","fra":"Pays-Bas caribéens","hrv":"Bonaire, Sint Eustatius i Saba","ita":"Paesi Bassi caraibici","jpn":"ボネール、シント・ユースタティウスおよびサバ","nld":"Caribisch Nederland","por":"Países Baixos Caribenhos","rus":"Карибские Нидерланды","slk":"Bonaire, Sint Eustatius a Saba","spa":"Caribe Neerlandés","fin":"Bonaire, Sint Eustatius ja Saba","est":"Bonaire, Sint Eustatius ja Saba","zho":"荷蘭加勒比區","pol":"Antyle Holenderskie","urd":"کیریبین نیدرلینڈز","kor":"카리브 네덜란드"}},"KY":{"currency":["KYD"],"callingCode":["1345"],"region":"Americas","subregion":"Caribbean","flag":"flag-ky","name":{"common":"Cayman Islands","ces":"Kajmanské ostrovy","cym":"Ynysoedd Cayman","deu":"Kaimaninseln","fra":"Îles Caïmans","hrv":"Kajmanski otoci","ita":"Isole Cayman","jpn":"ケイマン諸島","nld":"Caymaneilanden","por":"Ilhas Caimão","rus":"Каймановы острова","slk":"Kajmanie ostrovy","spa":"Islas Caimán","fin":"Caymansaaret","est":"Kaimanisaared","zho":"开曼群岛","pol":"Kajmany","urd":"جزائر کیمین","kor":"케이맨 제도"}},"CF":{"currency":["XAF"],"callingCode":["236"],"region":"Africa","subregion":"Middle Africa","flag":"flag-cf","name":{"common":"Central African Republic","ces":"Středoafrická republika","cym":"Gweriniaeth Canolbarth Affrica","deu":"Zentralafrikanische Republik","fra":"République centrafricaine","hrv":"Srednjoafrička Republika","ita":"Repubblica Centrafricana","jpn":"中央アフリカ共和国","nld":"Centraal-Afrikaanse Republiek","por":"República Centro-Africana","rus":"Центральноафриканская Республика","slk":"Stredoafrická republika","spa":"República Centroafricana","fin":"Keski-Afrikan tasavalta","est":"Kesk-Aafrika Vabariik","zho":"中非共和国","pol":"Republika Środkowoafrykańska","urd":"وسطی افریقی جمہوریہ","kor":"중앙아프리카 공화국"}},"TD":{"currency":["XAF"],"callingCode":["235"],"region":"Africa","subregion":"Middle Africa","flag":"flag-td","name":{"common":"Chad","ces":"Čad","cym":"Tsiad","deu":"Tschad","fra":"Tchad","hrv":"Čad","ita":"Ciad","jpn":"チャド","nld":"Tsjaad","por":"Chade","rus":"Чад","slk":"Čad","spa":"Chad","fin":"Tšad","est":"Tšaad","zho":"乍得","pol":"Czad","urd":"چاڈ","kor":"차드"}},"CL":{"currency":["CLP"],"callingCode":["56"],"region":"Americas","subregion":"South America","flag":"flag-cl","name":{"common":"Chile","ces":"Chile","cym":"Chile","deu":"Chile","fra":"Chili","hrv":"Čile","ita":"Cile","jpn":"チリ","nld":"Chili","por":"Chile","rus":"Чили","slk":"Čile","spa":"Chile","fin":"Chile","est":"Tšiili","zho":"智利","pol":"Chile","urd":"چلی","kor":"칠레"}},"CN":{"currency":["CNY"],"callingCode":["86"],"region":"Asia","subregion":"Eastern Asia","flag":"flag-cn","name":{"common":"China","ces":"Čína","cym":"Tsieina","deu":"China","fra":"Chine","hrv":"Kina","ita":"Cina","jpn":"中国","nld":"China","por":"China","rus":"Китай","slk":"Čína","spa":"China","fin":"Kiina","est":"Hiina","pol":"Chiny","urd":"چین","kor":"중국"}},"CX":{"currency":["AUD"],"callingCode":["61"],"region":"Oceania","subregion":"Australia and New Zealand","flag":"flag-cx","name":{"common":"Christmas Island","ces":"Vánoční ostrov","cym":"Ynys y Nadolig","deu":"Weihnachtsinsel","fra":"Île Christmas","hrv":"Božićni otok","ita":"Isola di Natale","jpn":"クリスマス島","nld":"Christmaseiland","por":"Ilha do Natal","rus":"Остров Рождества","slk":"Vianočnú ostrov","spa":"Isla de Navidad","fin":"Joulusaari","est":"Jõulusaar","zho":"圣诞岛","pol":"Wyspa Bożego Narodzenia","urd":"جزیرہ کرسمس","kor":"크리스마스 섬"}},"CC":{"currency":["AUD"],"callingCode":["61"],"region":"Oceania","subregion":"Australia and New Zealand","flag":"flag-cc","name":{"common":"Cocos (Keeling) Islands","ces":"Kokosové ostrovy","cym":"Ynysoedd Cocos","deu":"Kokosinseln","fra":"Îles Cocos","hrv":"Kokosovi Otoci","ita":"Isole Cocos e Keeling","jpn":"ココス(キーリング)諸島","nld":"Cocoseilanden","por":"Ilhas Cocos (Keeling)","rus":"Кокосовые острова","slk":"Kokosové ostrovy","spa":"Islas Cocos o Islas Keeling","fin":"Kookossaaret","est":"Kookossaared","zho":"科科斯","pol":"Wyspy Kokosowe","urd":"جزائر کوکوس","kor":"코코스 제도"}},"CO":{"currency":["COP"],"callingCode":["57"],"region":"Americas","subregion":"South America","flag":"flag-co","name":{"common":"Colombia","ces":"Kolumbie","cym":"Colombia","deu":"Kolumbien","fra":"Colombie","hrv":"Kolumbija","ita":"Colombia","jpn":"コロンビア","nld":"Colombia","por":"Colômbia","rus":"Колумбия","slk":"Kolumbia","spa":"Colombia","fin":"Kolumbia","est":"Colombia","zho":"哥伦比亚","pol":"Kolumbia","urd":"کولمبیا","kor":"콜롬비아"}},"KM":{"currency":["KMF"],"callingCode":["269"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-km","name":{"common":"Comoros","ces":"Komory","cym":"Y Comoros","deu":"Komoren","fra":"Comores","hrv":"Komori","ita":"Comore","jpn":"コモロ","nld":"Comoren","por":"Comores","rus":"Коморы","slk":"Komory","spa":"Comoras","fin":"Komorit","est":"Komoorid","zho":"科摩罗","pol":"Komory","urd":"القمری","kor":"코모로"}},"CK":{"currency":["NZD","CKD"],"callingCode":["682"],"region":"Oceania","subregion":"Polynesia","flag":"flag-ck","name":{"common":"Cook Islands","ces":"Cookovy ostrovy","cym":"Ynysoedd Cook","deu":"Cookinseln","fra":"Îles Cook","hrv":"Cookovo Otočje","ita":"Isole Cook","jpn":"クック諸島","nld":"Cookeilanden","por":"Ilhas Cook","rus":"Острова Кука","slk":"Cookove ostrovy","spa":"Islas Cook","fin":"Cookinsaaret","est":"Cooki saared","zho":"库克群岛","pol":"Wyspy Cooka","urd":"جزائر کک","kor":"쿡 제도"}},"CR":{"currency":["CRC"],"callingCode":["506"],"region":"Americas","subregion":"Central America","flag":"flag-cr","name":{"common":"Costa Rica","ces":"Kostarika","cym":"Costa Rica","deu":"Costa Rica","fra":"Costa Rica","hrv":"Kostarika","ita":"Costa Rica","jpn":"コスタリカ","nld":"Costa Rica","por":"Costa Rica","rus":"Коста-Рика","slk":"Kostarika","spa":"Costa Rica","fin":"Costa Rica","est":"Costa Rica","zho":"哥斯达黎加","pol":"Kostaryka","urd":"کوسٹاریکا","kor":"코스타리카"}},"HR":{"currency":["HRK"],"callingCode":["385"],"region":"Europe","subregion":"Southern Europe","flag":"flag-hr","name":{"common":"Croatia","ces":"Chorvatsko","cym":"Croatia","deu":"Kroatien","fra":"Croatie","hrv":"Hrvatska","ita":"Croazia","jpn":"クロアチア","nld":"Kroatië","por":"Croácia","rus":"Хорватия","slk":"Chorvátsko","spa":"Croacia","fin":"Kroatia","est":"Horvaatia","zho":"克罗地亚","pol":"Chorwacja","urd":"کرویئشا","kor":"크로아티아"}},"CU":{"currency":["CUC","CUP"],"callingCode":["53"],"region":"Americas","subregion":"Caribbean","flag":"flag-cu","name":{"common":"Cuba","ces":"Kuba","cym":"Ciwba","deu":"Kuba","fra":"Cuba","hrv":"Kuba","ita":"Cuba","jpn":"キューバ","nld":"Cuba","por":"Cuba","rus":"Куба","slk":"Kuba","spa":"Cuba","fin":"Kuuba","est":"Kuuba","zho":"古巴","pol":"Kuba","urd":"کیوبا","kor":"쿠바"}},"CW":{"currency":["ANG"],"callingCode":["5999"],"region":"Americas","subregion":"Caribbean","flag":"flag-cw","name":{"common":"Curaçao","ces":"Curaçao","deu":"Curaçao","fra":"Curaçao","nld":"Curaçao","por":"ilha da Curação","rus":"Кюрасао","slk":"Curacao","spa":"Curazao","fin":"Curaçao","est":"Curaçao","zho":"库拉索","pol":"Curaçao","urd":"کیوراساؤ","kor":"퀴라소"}},"CY":{"currency":["EUR"],"callingCode":["357"],"region":"Europe","subregion":"Eastern Europe","flag":"flag-cy","name":{"common":"Cyprus","ces":"Kypr","cym":"Cyprus","deu":"Zypern","fra":"Chypre","hrv":"Cipar","ita":"Cipro","jpn":"キプロス","nld":"Cyprus","por":"Chipre","rus":"Кипр","slk":"Cyprus","spa":"Chipre","fin":"Kypros","est":"Küpros","zho":"塞浦路斯","pol":"Cypr","urd":"قبرص","kor":"키프로스"}},"CZ":{"currency":["CZK"],"callingCode":["420"],"region":"Europe","subregion":"Eastern Europe","flag":"flag-cz","name":{"common":"Czechia","ces":"Česko","cym":"Y Weriniaeth Tsiec","deu":"Tschechien","fra":"Tchéquie","hrv":"Češka","ita":"Cechia","jpn":"チェコ","nld":"Tsjechië","por":"Chéquia","rus":"Чехия","slk":"Česko","spa":"Chequia","fin":"Tšekki","est":"Tšehhi","zho":"捷克","pol":"Czechy","urd":"چيک","kor":"체코"}},"CD":{"currency":["CDF"],"callingCode":["243"],"region":"Africa","subregion":"Middle Africa","flag":"flag-cd","name":{"common":"DR Congo","ces":"DR Kongo","cym":"Gweriniaeth Ddemocrataidd Congo","deu":"Kongo (Dem. Rep.)","fra":"Congo (Rép. dém.)","hrv":"Kongo, Demokratska Republika","ita":"Congo (Rep. Dem.)","jpn":"コンゴ民主共和国","nld":"Congo (DRC)","por":"República Democrática do Congo","rus":"Демократическая Республика Конго","slk":"Kongo","spa":"Congo (Rep. Dem.)","fin":"Kongon demokraattinen tasavalta","est":"Kongo DV","zho":"民主刚果","pol":"Demokratyczna Republika Konga","urd":"\nکانگو","kor":"콩고 민주 공화국"}},"DK":{"currency":["DKK"],"callingCode":["45"],"region":"Europe","subregion":"Northern Europe","flag":"flag-dk","name":{"common":"Denmark","ces":"Dánsko","cym":"Denmarc","deu":"Dänemark","fra":"Danemark","hrv":"Danska","ita":"Danimarca","jpn":"デンマーク","nld":"Denemarken","por":"Dinamarca","rus":"Дания","slk":"Dánsko","spa":"Dinamarca","fin":"Tanska","est":"Taani","zho":"丹麦","pol":"Dania","urd":"ڈنمارک","kor":"덴마크"}},"DJ":{"currency":["DJF"],"callingCode":["253"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-dj","name":{"common":"Djibouti","ces":"Džibutsko","cym":"Jibwti","deu":"Dschibuti","fra":"Djibouti","hrv":"Džibuti","ita":"Gibuti","jpn":"ジブチ","nld":"Djibouti","por":"Djibouti","rus":"Джибути","slk":"Džibutsko","spa":"Djibouti","fin":"Dijibouti","est":"Djibouti","zho":"吉布提","pol":"Dżibuti","urd":"جبوتی","kor":"지부티"}},"DM":{"currency":["XCD"],"callingCode":["1767"],"region":"Americas","subregion":"Caribbean","flag":"flag-dm","name":{"common":"Dominica","ces":"Dominika","cym":"Dominica","deu":"Dominica","fra":"Dominique","hrv":"Dominika","ita":"Dominica","jpn":"ドミニカ国","nld":"Dominica","por":"Dominica","rus":"Доминика","slk":"Dominika","spa":"Dominica","fin":"Dominica","est":"Dominica","zho":"多米尼加","pol":"Dominika","urd":"ڈومینیکا","kor":"도미니카 공화국"}},"DO":{"currency":["DOP"],"callingCode":["1809","1829","1849"],"region":"Americas","subregion":"Caribbean","flag":"flag-do","name":{"common":"Dominican Republic","ces":"Dominikánská republika","cym":"Gweriniaeth Dominica","deu":"Dominikanische Republik","fra":"République dominicaine","hrv":"Dominikanska Republika","ita":"Repubblica Dominicana","jpn":"ドミニカ共和国","nld":"Dominicaanse Republiek","por":"República Dominicana","rus":"Доминиканская Республика","slk":"Dominikánska republika","spa":"República Dominicana","fin":"Dominikaaninen tasavalta","est":"Dominikaani Vabariik","zho":"多明尼加","pol":"Dominikana","urd":"ڈومینیکن","kor":"도미니카 공화국"}},"EC":{"currency":["USD"],"callingCode":["593"],"region":"Americas","subregion":"South America","flag":"flag-ec","name":{"common":"Ecuador","ces":"Ekvádor","cym":"Ecwador","deu":"Ecuador","fra":"Équateur","hrv":"Ekvador","ita":"Ecuador","jpn":"エクアドル","nld":"Ecuador","por":"Equador","rus":"Эквадор","slk":"Ekvádor","spa":"Ecuador","fin":"Ecuador","est":"Ecuador","zho":"厄瓜多尔","pol":"Ekwador","urd":"ایکواڈور","kor":"에콰도르"}},"EG":{"currency":["EGP"],"callingCode":["20"],"region":"Africa","subregion":"Northern Africa","flag":"flag-eg","name":{"common":"Egypt","ces":"Egypt","cym":"Yr Aifft","deu":"Ägypten","fra":"Égypte","hrv":"Egipat","ita":"Egitto","jpn":"エジプト","nld":"Egypte","por":"Egito","rus":"Египет","slk":"Egypt","spa":"Egipto","fin":"Egypti","est":"Egiptus","zho":"埃及","pol":"Egipt","urd":"مصر","kor":"이집트"}},"SV":{"currency":["SVC","USD"],"callingCode":["503"],"region":"Americas","subregion":"Central America","flag":"flag-sv","name":{"common":"El Salvador","ces":"Salvador","cym":"El Salfador","deu":"El Salvador","fra":"Salvador","hrv":"Salvador","ita":"El Salvador","jpn":"エルサルバドル","nld":"El Salvador","por":"El Salvador","rus":"Сальвадор","slk":"Salvádor","spa":"El Salvador","fin":"El Salvador","est":"El Salvador","zho":"萨尔瓦多","pol":"Salwador","urd":"ایل سیلواڈور","kor":"엘살바도르"}},"GQ":{"currency":["XAF"],"callingCode":["240"],"region":"Africa","subregion":"Middle Africa","flag":"flag-gq","name":{"common":"Equatorial Guinea","ces":"Rovníková Guinea","cym":"Gini Gyhydeddol","deu":"Äquatorialguinea","fra":"Guinée équatoriale","hrv":"Ekvatorijalna Gvineja","ita":"Guinea Equatoriale","jpn":"赤道ギニア","nld":"Equatoriaal-Guinea","por":"Guiné Equatorial","rus":"Экваториальная Гвинея","slk":"Rovníková Guinea","spa":"Guinea Ecuatorial","fin":"Päiväntasaajan Guinea","est":"Ekvatoriaal-Guinea","zho":"赤道几内亚","pol":"Gwinea Równikowa","urd":"استوائی گنی","kor":"적도 기니"}},"ER":{"currency":["ERN"],"callingCode":["291"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-er","name":{"common":"Eritrea","ces":"Eritrea","cym":"Eritrea","deu":"Eritrea","fra":"Érythrée","hrv":"Eritreja","ita":"Eritrea","jpn":"エリトリア","nld":"Eritrea","por":"Eritreia","rus":"Эритрея","slk":"Eritrea","spa":"Eritrea","fin":"Eritrea","est":"Eritrea","zho":"厄立特里亚","pol":"Erytrea","urd":"ارتریا","kor":"에리트레아"}},"EE":{"currency":["EUR"],"callingCode":["372"],"region":"Europe","subregion":"Northern Europe","flag":"flag-ee","name":{"common":"Estonia","ces":"Estonsko","cym":"Estonia","deu":"Estland","fra":"Estonie","hrv":"Estonija","ita":"Estonia","jpn":"エストニア","nld":"Estland","por":"Estónia","rus":"Эстония","slk":"Estónsko","spa":"Estonia","fin":"Viro","est":"Eesti","zho":"爱沙尼亚","pol":"Estonia","urd":"اسٹونیا","kor":"에스토니아"}},"SZ":{"currency":["SZL"],"callingCode":["268"],"region":"Africa","subregion":"Southern Africa","flag":"flag-sz","name":{"common":"Eswatini","ces":"Svazijsko","deu":"Swasiland","fra":"Swaziland","hrv":"Svazi","ita":"Swaziland","jpn":"スワジランド","nld":"Swaziland","por":"Suazilândia","rus":"Свазиленд","slk":"Svazijsko","spa":"Suazilandia","fin":"Swazimaa","est":"Svaasimaa","pol":"Suazi","zho":"斯威士兰","urd":"سوازی لینڈ","kor":"에스와티니"}},"ET":{"currency":["ETB"],"callingCode":["251"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-et","name":{"common":"Ethiopia","ces":"Etiopie","cym":"Ethiopia","deu":"Äthiopien","fra":"Éthiopie","hrv":"Etiopija","ita":"Etiopia","jpn":"エチオピア","nld":"Ethiopië","por":"Etiópia","rus":"Эфиопия","slk":"Etiópia","spa":"Etiopía","fin":"Etiopia","est":"Etioopia","zho":"埃塞俄比亚","pol":"Etiopia","urd":"ایتھوپیا","kor":"에티오피아"}},"FK":{"currency":["FKP"],"callingCode":["500"],"region":"Americas","subregion":"South America","flag":"flag-fk","name":{"common":"Falkland Islands","ces":"Falklandy","deu":"Falklandinseln","fra":"Îles Malouines","hrv":"Falklandski Otoci","ita":"Isole Falkland o Isole Malvine","jpn":"フォークランド(マルビナス)諸島","nld":"Falklandeilanden","por":"Ilhas Malvinas","rus":"Фолклендские острова","slk":"Falklandy","spa":"Islas Malvinas","fin":"Falkandinsaaret","est":"Falklandi saared","zho":"福克兰群岛","pol":"Falklandy","urd":"جزائر فاکلینڈ","kor":"포클랜드 제도"}},"FO":{"currency":["DKK"],"callingCode":["298"],"region":"Europe","subregion":"Northern Europe","flag":"flag-fo","name":{"common":"Faroe Islands","ces":"Faerské ostrovy","deu":"Färöer-Inseln","fra":"Îles Féroé","hrv":"Farski Otoci","ita":"Isole Far Oer","jpn":"フェロー諸島","nld":"Faeröer","por":"Ilhas Faroé","rus":"Фарерские острова","slk":"Faerské ostrovy","spa":"Islas Faroe","fin":"Färsaaret","est":"Fääri saared","zho":"法罗群岛","pol":"Wyspy Owcze","urd":"جزائر فارو","kor":"페로 제도"}},"FJ":{"currency":["FJD"],"callingCode":["679"],"region":"Oceania","subregion":"Melanesia","flag":"flag-fj","name":{"common":"Fiji","ces":"Fidži","deu":"Fidschi","fra":"Fidji","hrv":"Fiđi","ita":"Figi","jpn":"フィジー","nld":"Fiji","por":"Fiji","rus":"Фиджи","slk":"Fidži","spa":"Fiyi","fin":"Fidži","est":"Fidži","zho":"斐济","pol":"Fidżi","urd":"فجی","kor":"피지"}},"FI":{"currency":["EUR"],"callingCode":["358"],"region":"Europe","subregion":"Northern Europe","flag":"flag-fi","name":{"common":"Finland","ces":"Finsko","deu":"Finnland","fra":"Finlande","hrv":"Finska","ita":"Finlandia","jpn":"フィンランド","nld":"Finland","por":"Finlândia","rus":"Финляндия","slk":"Fínsko","spa":"Finlandia","fin":"Suomi","est":"Soome","zho":"芬兰","pol":"Finlandia","urd":"فن لینڈ","kor":"핀란드"}},"FR":{"currency":["EUR"],"callingCode":["33"],"region":"Europe","subregion":"Western Europe","flag":"flag-fr","name":{"common":"France","ces":"Francie","deu":"Frankreich","fra":"France","hrv":"Francuska","ita":"Francia","jpn":"フランス","nld":"Frankrijk","por":"França","rus":"Франция","slk":"Francúzsko","spa":"Francia","fin":"Ranska","est":"Prantsusmaa","zho":"法国","pol":"Francja","urd":"فرانس","kor":"프랑스"}},"GF":{"currency":["EUR"],"callingCode":["594"],"region":"Americas","subregion":"South America","flag":"flag-gf","name":{"common":"French Guiana","ces":"Francouzská Guyana","deu":"Französisch-Guayana","fra":"Guyane","hrv":"Francuska Gvajana","ita":"Guyana francese","jpn":"フランス領ギアナ","nld":"Frans-Guyana","por":"Guiana Francesa","rus":"Французская Гвиана","slk":"Guyana","spa":"Guayana Francesa","fin":"Ranskan Guayana","est":"Prantsuse Guajaana","zho":"法属圭亚那","pol":"Gujana Francuska","urd":"فرانسیسی گیانا","kor":"프랑스령 기아나"}},"PF":{"currency":["XPF"],"callingCode":["689"],"region":"Oceania","subregion":"Polynesia","flag":"flag-pf","name":{"common":"French Polynesia","ces":"Francouzská Polynésie","deu":"Französisch-Polynesien","fra":"Polynésie française","hrv":"Francuska Polinezija","ita":"Polinesia Francese","jpn":"フランス領ポリネシア","nld":"Frans-Polynesië","por":"Polinésia Francesa","rus":"Французская Полинезия","slk":"Francúzska Polynézia","spa":"Polinesia Francesa","fin":"Ranskan Polynesia","est":"Prantsuse Polüneesia","zho":"法属波利尼西亚","pol":"Polinezja Francuska","urd":"فرانسیسی پولینیشیا","kor":"프랑스령 폴리네시아"}},"TF":{"currency":["EUR"],"callingCode":[],"region":"Antarctic","subregion":"","flag":"flag-tf","name":{"common":"French Southern and Antarctic Lands","ces":"Francouzská jižní a antarktická území","deu":"Französische Süd- und Antarktisgebiete","fra":"Terres australes et antarctiques françaises","hrv":"Francuski južni i antarktički teritoriji","ita":"Territori Francesi del Sud","jpn":"フランス領南方・南極地域","nld":"Franse Gebieden in de zuidelijke Indische Oceaan","por":"Terras Austrais e Antárticas Francesas","rus":"Французские Южные и Антарктические территории","slk":"Francúzske juŽné a antarktické územia","spa":"Tierras Australes y Antárticas Francesas","fin":"Ranskan eteläiset ja antarktiset alueet","est":"Prantsuse Lõunaalad","zho":"法国南部和南极土地","pol":"Francuskie Terytoria Południowe i Antarktyczne","urd":"سرزمین جنوبی فرانسیسیہ و انٹارکٹیکا","kor":"프랑스령 남부와 남극 지역"}},"GA":{"currency":["XAF"],"callingCode":["241"],"region":"Africa","subregion":"Middle Africa","flag":"flag-ga","name":{"common":"Gabon","ces":"Gabon","deu":"Gabun","fra":"Gabon","hrv":"Gabon","ita":"Gabon","jpn":"ガボン","nld":"Gabon","por":"Gabão","rus":"Габон","slk":"Gabon","spa":"Gabón","fin":"Gabon","est":"Gabon","zho":"加蓬","pol":"Gabon","urd":"گیبون","kor":"가봉"}},"GM":{"currency":["GMD"],"callingCode":["220"],"region":"Africa","subregion":"Western Africa","flag":"flag-gm","name":{"common":"Gambia","ces":"Gambie","deu":"Gambia","fra":"Gambie","hrv":"Gambija","ita":"Gambia","jpn":"ガンビア","nld":"Gambia","por":"Gâmbia","rus":"Гамбия","slk":"Gambia","spa":"Gambia","fin":"Gambia","est":"Gambia","zho":"冈比亚","pol":"Gambia","urd":"گیمبیا","kor":"감비아"}},"GE":{"currency":["GEL"],"callingCode":["995"],"region":"Asia","subregion":"Western Asia","flag":"flag-ge","name":{"common":"Georgia","ces":"Gruzie","deu":"Georgien","fra":"Géorgie","hrv":"Gruzija","ita":"Georgia","jpn":"グルジア","nld":"Georgië","por":"Geórgia","rus":"Грузия","slk":"Gruzínsko","spa":"Georgia","fin":"Georgia","est":"Gruusia","zho":"格鲁吉亚","pol":"Gruzja","urd":"جارجیا","kor":"조지아"}},"DE":{"currency":["EUR"],"callingCode":["49"],"region":"Europe","subregion":"Western Europe","flag":"flag-de","name":{"common":"Germany","ces":"Německo","deu":"Deutschland","fra":"Allemagne","hrv":"Njemačka","ita":"Germania","jpn":"ドイツ","nld":"Duitsland","por":"Alemanha","rus":"Германия","slk":"Nemecko","spa":"Alemania","fin":"Saksa","est":"Saksamaa","zho":"德国","pol":"Niemcy","urd":"جرمنی","kor":"독일"}},"GH":{"currency":["GHS"],"callingCode":["233"],"region":"Africa","subregion":"Western Africa","flag":"flag-gh","name":{"common":"Ghana","ces":"Ghana","deu":"Ghana","fra":"Ghana","hrv":"Gana","ita":"Ghana","jpn":"ガーナ","nld":"Ghana","por":"Gana","rus":"Гана","slk":"Ghana","spa":"Ghana","fin":"Ghana","est":"Ghana","zho":"加纳","pol":"Ghana","urd":"گھانا","kor":"가나"}},"GI":{"currency":["GIP"],"callingCode":["350"],"region":"Europe","subregion":"Southern Europe","flag":"flag-gi","name":{"common":"Gibraltar","ces":"Gibraltar","deu":"Gibraltar","fra":"Gibraltar","hrv":"Gibraltar","ita":"Gibilterra","jpn":"ジブラルタル","nld":"Gibraltar","por":"Gibraltar","rus":"Гибралтар","slk":"Gibraltár","spa":"Gibraltar","fin":"Gibraltar","est":"Gibraltar","zho":"直布罗陀","pol":"Gibraltar","urd":"جبل الطارق","kor":"지브롤터"}},"GR":{"currency":["EUR"],"callingCode":["30"],"region":"Europe","subregion":"Southern Europe","flag":"flag-gr","name":{"common":"Greece","ces":"Řecko","deu":"Griechenland","fra":"Grèce","hrv":"Grčka","ita":"Grecia","jpn":"ギリシャ","nld":"Griekenland","por":"Grécia","rus":"Греция","slk":"Greécko","spa":"Grecia","fin":"Kreikka","est":"Kreeka","zho":"希腊","pol":"Grecja","urd":"یونان","kor":"그리스"}},"GL":{"currency":["DKK"],"callingCode":["299"],"region":"Americas","subregion":"North America","flag":"flag-gl","name":{"common":"Greenland","ces":"Grónsko","deu":"Grönland","fra":"Groenland","hrv":"Grenland","ita":"Groenlandia","jpn":"グリーンランド","nld":"Groenland","por":"Gronelândia","rus":"Гренландия","slk":"Grónsko","spa":"Groenlandia","fin":"Groönlanti","est":"Gröönimaa","zho":"格陵兰","pol":"Grenlandia","urd":"گرین لینڈ","kor":"그린란드"}},"GD":{"currency":["XCD"],"callingCode":["1473"],"region":"Americas","subregion":"Caribbean","flag":"flag-gd","name":{"common":"Grenada","ces":"Grenada","deu":"Grenada","fra":"Grenade","hrv":"Grenada","ita":"Grenada","jpn":"グレナダ","nld":"Grenada","por":"Granada","rus":"Гренада","slk":"Grenada","spa":"Grenada","fin":"Grenada","est":"Grenada","zho":"格林纳达","pol":"Grenada","urd":"گریناڈا","kor":"그레나다"}},"GP":{"currency":["EUR"],"callingCode":["590"],"region":"Americas","subregion":"Caribbean","flag":"flag-gp","name":{"common":"Guadeloupe","ces":"Guadeloupe","deu":"Guadeloupe","fra":"Guadeloupe","hrv":"Gvadalupa","ita":"Guadeloupa","jpn":"グアドループ","nld":"Guadeloupe","por":"Guadalupe","rus":"Гваделупа","slk":"Guadeloupe","spa":"Guadalupe","fin":"Guadeloupe","est":"Guadeloupe","zho":"瓜德罗普岛","pol":"Gwadelupa","urd":"گواڈیلوپ","kor":"과들루프"}},"GU":{"currency":["USD"],"callingCode":["1671"],"region":"Oceania","subregion":"Micronesia","flag":"flag-gu","name":{"common":"Guam","ces":"Guam","deu":"Guam","fra":"Guam","hrv":"Guam","ita":"Guam","jpn":"グアム","nld":"Guam","por":"Guam","rus":"Гуам","slk":"Guam","spa":"Guam","fin":"Guam","est":"Guam","zho":"关岛","pol":"Guam","urd":"گوام","kor":"괌"}},"GT":{"currency":["GTQ"],"callingCode":["502"],"region":"Americas","subregion":"Central America","flag":"flag-gt","name":{"common":"Guatemala","ces":"Guatemala","deu":"Guatemala","fra":"Guatemala","hrv":"Gvatemala","ita":"Guatemala","jpn":"グアテマラ","nld":"Guatemala","por":"Guatemala","rus":"Гватемала","slk":"Guatemala","spa":"Guatemala","fin":"Guatemala","est":"Guatemala","zho":"危地马拉","pol":"Gwatemala","urd":"گواتیمالا","kor":"과테말라"}},"GG":{"currency":["GBP"],"callingCode":["44"],"region":"Europe","subregion":"Northern Europe","flag":"flag-gg","name":{"common":"Guernsey","ces":"Guernsey","deu":"Guernsey","fra":"Guernesey","hrv":"Guernsey","ita":"Guernsey","jpn":"ガーンジー","nld":"Guernsey","por":"Guernsey","rus":"Гернси","slk":"Guernsey","spa":"Guernsey","fin":"Guernsey","est":"Guernsey","zho":"根西岛","pol":"Guernsey","urd":"گرنزی","kor":"건지 섬"}},"GN":{"currency":["GNF"],"callingCode":["224"],"region":"Africa","subregion":"Western Africa","flag":"flag-gn","name":{"common":"Guinea","ces":"Guinea","deu":"Guinea","fra":"Guinée","hrv":"Gvineja","ita":"Guinea","jpn":"ギニア","nld":"Guinee","por":"Guiné","rus":"Гвинея","slk":"Guinea","spa":"Guinea","fin":"Guinea","est":"Guinea","zho":"几内亚","pol":"Gwinea","urd":"گنی","kor":"기니"}},"GW":{"currency":["XOF"],"callingCode":["245"],"region":"Africa","subregion":"Western Africa","flag":"flag-gw","name":{"common":"Guinea-Bissau","ces":"Guinea-Bissau","deu":"Guinea-Bissau","fra":"Guinée-Bissau","hrv":"Gvineja Bisau","ita":"Guinea-Bissau","jpn":"ギニアビサウ","nld":"Guinee-Bissau","por":"Guiné-Bissau","rus":"Гвинея-Бисау","slk":"Guinea-Bissau","spa":"Guinea-Bisáu","fin":"Guinea-Bissau","est":"Guinea-Bissau","zho":"几内亚比绍","pol":"Gwinea Bissau","urd":"گنی بساؤ","kor":"기니비사우"}},"GY":{"currency":["GYD"],"callingCode":["592"],"region":"Americas","subregion":"South America","flag":"flag-gy","name":{"common":"Guyana","ces":"Guyana","deu":"Guyana","fra":"Guyana","hrv":"Gvajana","ita":"Guyana","jpn":"ガイアナ","nld":"Guyana","por":"Guiana","rus":"Гайана","slk":"Guyana","spa":"Guyana","fin":"Guayana","est":"Guyana","zho":"圭亚那","pol":"Gujana","urd":"گیانا","kor":"가이아나"}},"HT":{"currency":["HTG","USD"],"callingCode":["509"],"region":"Americas","subregion":"Caribbean","flag":"flag-ht","name":{"common":"Haiti","ces":"Haiti","deu":"Haiti","fra":"Haïti","hrv":"Haiti","ita":"Haiti","jpn":"ハイチ","nld":"Haïti","por":"Haiti","rus":"Гаити","slk":"Haiti","spa":"Haiti","fin":"Haiti","est":"Haiti","zho":"海地","pol":"Haiti","urd":"ہیٹی","kor":"아이티"}},"HM":{"currency":["AUD"],"callingCode":[],"region":"Antarctic","subregion":"","flag":"flag-hm","name":{"common":"Heard Island and McDonald Islands","ces":"Heardův ostrov a McDonaldovy ostrovy","deu":"Heard und die McDonaldinseln","fra":"Îles Heard-et-MacDonald","hrv":"Otok Heard i otočje McDonald","ita":"Isole Heard e McDonald","jpn":"ハード島とマクドナルド諸島","nld":"Heard-en McDonaldeilanden","por":"Ilha Heard e Ilhas McDonald","rus":"Остров Херд и острова Макдональд","slk":"Heardov ostrov","spa":"Islas Heard y McDonald","fin":"Heard ja McDonaldinsaaret","est":"Heard ja McDonald","zho":"赫德岛和麦当劳群岛","pol":"Wyspy Heard i McDonalda","urd":"جزیرہ ہرڈ و جزائر مکڈونلڈ","kor":"허드 맥도널드 제도"}},"HN":{"currency":["HNL"],"callingCode":["504"],"region":"Americas","subregion":"Central America","flag":"flag-hn","name":{"common":"Honduras","ces":"Honduras","deu":"Honduras","fra":"Honduras","hrv":"Honduras","ita":"Honduras","jpn":"ホンジュラス","nld":"Honduras","por":"Honduras","rus":"Гондурас","slk":"Honduras","spa":"Honduras","fin":"Honduras","est":"Honduras","zho":"洪都拉斯","pol":"Honduras","urd":"ہونڈوراس","kor":"온두라스"}},"HK":{"currency":["HKD"],"callingCode":["852"],"region":"Asia","subregion":"Eastern Asia","flag":"flag-hk","name":{"common":"Hong Kong","ces":"Hongkong","deu":"Hongkong","fra":"Hong Kong","hrv":"Hong Kong","ita":"Hong Kong","jpn":"香港","nld":"Hongkong","por":"Hong Kong","rus":"Гонконг","slk":"Hongkong","spa":"Hong Kong","fin":"Hongkong","est":"Hongkong","pol":"Hongkong","urd":"ہانگ کانگ","kor":"홍콩"}},"HU":{"currency":["HUF"],"callingCode":["36"],"region":"Europe","subregion":"Eastern Europe","flag":"flag-hu","name":{"common":"Hungary","ces":"Maďarsko","deu":"Ungarn","fra":"Hongrie","hrv":"Mađarska","ita":"Ungheria","jpn":"ハンガリー","nld":"Hongarije","por":"Hungria","rus":"Венгрия","slk":"Maďarsko","spa":"Hungría","fin":"Unkari","est":"Ungari","zho":"匈牙利","pol":"Węgry","urd":"مجارستان","kor":"헝가리"}},"IS":{"currency":["ISK"],"callingCode":["354"],"region":"Europe","subregion":"Northern Europe","flag":"flag-is","name":{"common":"Iceland","ces":"Island","deu":"Island","fra":"Islande","hrv":"Island","ita":"Islanda","jpn":"アイスランド","nld":"IJsland","por":"Islândia","rus":"Исландия","slk":"Island","spa":"Islandia","fin":"Islanti","est":"Island","zho":"冰岛","pol":"Islandia","urd":"آئس لینڈ","kor":"아이슬란드"}},"IN":{"currency":["INR"],"callingCode":["91"],"region":"Asia","subregion":"Southern Asia","flag":"flag-in","name":{"common":"India","ces":"Indie","deu":"Indien","fra":"Inde","hrv":"Indija","ita":"India","jpn":"インド","nld":"India","por":"Índia","rus":"Индия","slk":"India","spa":"India","fin":"Intia","est":"India","zho":"印度","pol":"Indie","urd":"بھارت","kor":"인도"}},"ID":{"currency":["IDR"],"callingCode":["62"],"region":"Asia","subregion":"South-Eastern Asia","flag":"flag-id","name":{"common":"Indonesia","ces":"Indonésie","deu":"Indonesien","fra":"Indonésie","hrv":"Indonezija","ita":"Indonesia","jpn":"インドネシア","nld":"Indonesië","por":"Indonésia","rus":"Индонезия","slk":"Indonézia","spa":"Indonesia","fin":"Indonesia","est":"Indoneesia","zho":"印度尼西亚","pol":"Indonezja","urd":"انڈونیشیا","kor":"인도네시아"}},"IR":{"currency":["IRR"],"callingCode":["98"],"region":"Asia","subregion":"Southern Asia","flag":"flag-ir","name":{"common":"Iran","ces":"Írán","deu":"Iran","fra":"Iran","hrv":"Iran","ita":"Iran","jpn":"イラン・イスラム共和国","nld":"Iran","por":"Irão","rus":"Иран","slk":"Irán","spa":"Iran","fin":"Iran","est":"Iraan","zho":"伊朗","pol":"Iran","urd":"ایران","kor":"이란"}},"IQ":{"currency":["IQD"],"callingCode":["964"],"region":"Asia","subregion":"Western Asia","flag":"flag-iq","name":{"common":"Iraq","ces":"Irák","deu":"Irak","fra":"Irak","hrv":"Irak","ita":"Iraq","jpn":"イラク","nld":"Irak","por":"Iraque","rus":"Ирак","slk":"Irak","spa":"Irak","fin":"Irak","est":"Iraak","zho":"伊拉克","pol":"Irak","urd":"عراق","kor":"이라크"}},"IE":{"currency":["EUR"],"callingCode":["353"],"region":"Europe","subregion":"Northern Europe","flag":"flag-ie","name":{"common":"Ireland","ces":"Irsko","deu":"Irland","fra":"Irlande","hrv":"Irska","ita":"Irlanda","jpn":"アイルランド","nld":"Ierland","por":"Irlanda","rus":"Ирландия","slk":"Írsko","spa":"Irlanda","fin":"Irlanti","est":"Iirimaa","zho":"爱尔兰","pol":"Irlandia","urd":"جزیرہ آئرلینڈ","kor":"아일랜드"}},"IM":{"currency":["GBP"],"callingCode":["44"],"region":"Europe","subregion":"Northern Europe","flag":"flag-im","name":{"common":"Isle of Man","ces":"Ostrov Man","deu":"Insel Man","fra":"Île de Man","hrv":"Otok Man","ita":"Isola di Man","jpn":"マン島","nld":"Isle of Man","por":"Ilha de Man","rus":"Остров Мэн","slk":"Man","spa":"Isla de Man","fin":"Mansaari","est":"Mani saar","zho":"马恩岛","pol":"Wyspa Man","urd":"آئل آف مین","kor":"맨섬"}},"IL":{"currency":["ILS"],"callingCode":["972"],"region":"Asia","subregion":"Western Asia","flag":"flag-il","name":{"common":"Israel","ces":"Izrael","deu":"Israel","fra":"Israël","hrv":"Izrael","ita":"Israele","jpn":"イスラエル","nld":"Israël","por":"Israel","rus":"Израиль","slk":"Izrael","spa":"Israel","fin":"Israel","est":"Iisrael","zho":"以色列","pol":"Izrael","urd":"اسرائیل","kor":"이스라엘"}},"IT":{"currency":["EUR"],"callingCode":["39"],"region":"Europe","subregion":"Southern Europe","flag":"flag-it","name":{"common":"Italy","ces":"Itálie","deu":"Italien","fra":"Italie","hrv":"Italija","ita":"Italia","jpn":"イタリア","nld":"Italië","por":"Itália","rus":"Италия","slk":"Taliansko","spa":"Italia","fin":"Italia","est":"Itaalia","zho":"意大利","pol":"Włochy","urd":"اطالیہ","kor":"이탈리아"}},"CI":{"currency":["XOF"],"callingCode":["225"],"region":"Africa","subregion":"Western Africa","flag":"flag-ci","name":{"common":"Ivory Coast","ces":"Pobřeží slonoviny","deu":"Elfenbeinküste","fra":"Côte d'Ivoire","hrv":"Obala Bjelokosti","ita":"Costa d'Avorio","jpn":"コートジボワール","nld":"Ivoorkust","por":"Costa do Marfim","rus":"Кот-д’Ивуар","slk":"Pobržie Slonoviny","spa":"Costa de Marfil","fin":"Norsunluurannikko","est":"Elevandiluurannik","zho":"科特迪瓦","pol":"WybrzeŻe Kości Słoniowej","urd":"آئیوری کوسٹ","kor":"코트디부아르"}},"JM":{"currency":["JMD"],"callingCode":["1876"],"region":"Americas","subregion":"Caribbean","flag":"flag-jm","name":{"common":"Jamaica","ces":"Jamajka","deu":"Jamaika","fra":"Jamaïque","hrv":"Jamajka","ita":"Giamaica","jpn":"ジャマイカ","nld":"Jamaica","por":"Jamaica","rus":"Ямайка","slk":"Jamajka","spa":"Jamaica","fin":"Jamaika","est":"Jamaica","zho":"牙买加","pol":"Jamajka","urd":"جمیکا","kor":"자메이카"}},"JP":{"currency":["JPY"],"callingCode":["81"],"region":"Asia","subregion":"Eastern Asia","flag":"flag-jp","name":{"common":"Japan","ces":"Japonsko","deu":"Japan","fra":"Japon","hrv":"Japan","ita":"Giappone","jpn":"日本","nld":"Japan","por":"Japão","rus":"Япония","slk":"Japonsko","spa":"Japón","fin":"Japani","est":"Jaapan","zho":"日本","pol":"Japonia","urd":"جاپان","kor":"일본"}},"JE":{"currency":["GBP"],"callingCode":["44"],"region":"Europe","subregion":"Northern Europe","flag":"flag-je","name":{"common":"Jersey","ces":"Jersey","deu":"Jersey","fra":"Jersey","hrv":"Jersey","ita":"Isola di Jersey","jpn":"ジャージー","nld":"Jersey","por":"Jersey","rus":"Джерси","slk":"Jersey","spa":"Jersey","fin":"Jersey","est":"Jersey","zho":"泽西岛","pol":"Jersey","urd":"جرزی","kor":"저지 섬"}},"JO":{"currency":["JOD"],"callingCode":["962"],"region":"Asia","subregion":"Western Asia","flag":"flag-jo","name":{"common":"Jordan","ces":"Jordánsko","deu":"Jordanien","fra":"Jordanie","hrv":"Jordan","ita":"Giordania","jpn":"ヨルダン","nld":"Jordanië","por":"Jordânia","rus":"Иордания","slk":"Jordánsko","spa":"Jordania","fin":"Jordania","est":"Jordaania","zho":"约旦","pol":"Jordania","urd":"اردن","kor":"요르단"}},"KZ":{"currency":["KZT"],"callingCode":["7"],"region":"Asia","subregion":"Central Asia","flag":"flag-kz","name":{"common":"Kazakhstan","ces":"Kazachstán","deu":"Kasachstan","fra":"Kazakhstan","hrv":"Kazahstan","ita":"Kazakistan","jpn":"カザフスタン","nld":"Kazachstan","por":"Cazaquistão","rus":"Казахстан","slk":"Kazachstan","spa":"Kazajistán","fin":"Kazakstan","est":"Kasahstan","zho":"哈萨克斯坦","pol":"Kazachstan","urd":"قازقستان","kor":"카자흐스탄"}},"KE":{"currency":["KES"],"callingCode":["254"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-ke","name":{"common":"Kenya","ces":"Keňa","deu":"Kenia","fra":"Kenya","hrv":"Kenija","ita":"Kenya","jpn":"ケニア","nld":"Kenia","por":"Quénia","rus":"Кения","slk":"Keňa","spa":"Kenia","fin":"Kenia","est":"Keenia","zho":"肯尼亚","pol":"Kenia","urd":"کینیا","kor":"케냐"}},"KI":{"currency":["AUD"],"callingCode":["686"],"region":"Oceania","subregion":"Micronesia","flag":"flag-ki","name":{"common":"Kiribati","ces":"Kiribati","deu":"Kiribati","fra":"Kiribati","hrv":"Kiribati","ita":"Kiribati","jpn":"キリバス","nld":"Kiribati","por":"Kiribati","rus":"Кирибати","slk":"Kiribati","spa":"Kiribati","fin":"Kiribati","est":"Kiribati","zho":"基里巴斯","pol":"Kiribati","urd":"کیریباتی","kor":"키리바시"}},"XK":{"currency":["EUR"],"callingCode":["383"],"region":"Europe","subregion":"Eastern Europe","flag":"flag-xk","name":{"common":"Kosovo","ces":"Kosovo","deu":"Kosovo","fra":"Kosovo","hrv":"Kosovo","ita":"Kosovo","nld":"Kosovo","por":"Kosovo","rus":"Республика Косово","slk":"Kosovo","spa":"Kosovo","fin":"Kosovo","est":"Kosovo","zho":"科索沃","pol":"Kosowo","urd":"کوسووہ","kor":"코소보"}},"KW":{"currency":["KWD"],"callingCode":["965"],"region":"Asia","subregion":"Western Asia","flag":"flag-kw","name":{"common":"Kuwait","ces":"Kuvajt","deu":"Kuwait","fra":"Koweït","hrv":"Kuvajt","ita":"Kuwait","jpn":"クウェート","nld":"Koeweit","por":"Kuwait","rus":"Кувейт","slk":"Kuvajt","spa":"Kuwait","fin":"Kuwait","est":"Kuveit","zho":"科威特","pol":"Kuwejt","urd":"کویت","kor":"쿠웨이트"}},"KG":{"currency":["KGS"],"callingCode":["996"],"region":"Asia","subregion":"Central Asia","flag":"flag-kg","name":{"common":"Kyrgyzstan","ces":"Kyrgyzstán","deu":"Kirgisistan","fra":"Kirghizistan","hrv":"Kirgistan","ita":"Kirghizistan","jpn":"キルギス","nld":"Kirgizië","por":"Quirguistão","rus":"Киргизия","slk":"Kirgizsko","spa":"Kirguizistán","fin":"Kirgisia","est":"Kõrgõzstan","zho":"吉尔吉斯斯坦","pol":"Kirgistan","urd":"کرغیزستان","kor":"키르기스스탄"}},"LA":{"currency":["LAK"],"callingCode":["856"],"region":"Asia","subregion":"South-Eastern Asia","flag":"flag-la","name":{"common":"Laos","ces":"Laos","deu":"Laos","fra":"Laos","hrv":"Laos","ita":"Laos","jpn":"ラオス人民民主共和国","nld":"Laos","por":"Laos","rus":"Лаос","slk":"Laos","spa":"Laos","fin":"Laos","est":"Laos","zho":"老挝","pol":"Laos","urd":"لاؤس","kor":"라오스"}},"LV":{"currency":["EUR"],"callingCode":["371"],"region":"Europe","subregion":"Northern Europe","flag":"flag-lv","name":{"common":"Latvia","ces":"Lotyšsko","deu":"Lettland","fra":"Lettonie","hrv":"Latvija","ita":"Lettonia","jpn":"ラトビア","nld":"Letland","por":"Letónia","rus":"Латвия","slk":"Lotyšsko","spa":"Letonia","fin":"Latvia","est":"Läti","zho":"拉脱维亚","pol":"Łotwa","urd":"لٹویا","kor":"라트비아"}},"LB":{"currency":["LBP"],"callingCode":["961"],"region":"Asia","subregion":"Western Asia","flag":"flag-lb","name":{"common":"Lebanon","ces":"Libanon","deu":"Libanon","fra":"Liban","hrv":"Libanon","ita":"Libano","jpn":"レバノン","nld":"Libanon","por":"Líbano","rus":"Ливан","slk":"Libanon","spa":"Líbano","fin":"Libanon","est":"Liibanon","zho":"黎巴嫩","pol":"Liban","urd":"لبنان","kor":"레바논"}},"LS":{"currency":["LSL","ZAR"],"callingCode":["266"],"region":"Africa","subregion":"Southern Africa","flag":"flag-ls","name":{"common":"Lesotho","ces":"Lesotho","deu":"Lesotho","fra":"Lesotho","hrv":"Lesoto","ita":"Lesotho","jpn":"レソト","nld":"Lesotho","por":"Lesoto","rus":"Лесото","slk":"Lesotho","spa":"Lesotho","fin":"Lesotho","est":"Lesotho","zho":"莱索托","pol":"Lesotho","urd":"لیسوتھو","kor":"레소토"}},"LR":{"currency":["LRD"],"callingCode":["231"],"region":"Africa","subregion":"Western Africa","flag":"flag-lr","name":{"common":"Liberia","ces":"Libérie","deu":"Liberia","fra":"Liberia","hrv":"Liberija","ita":"Liberia","jpn":"リベリア","nld":"Liberia","por":"Libéria","rus":"Либерия","slk":"Libéria","spa":"Liberia","fin":"Liberia","est":"Libeeria","zho":"利比里亚","pol":"Liberia","urd":"لائبیریا","kor":"라이베리아"}},"LY":{"currency":["LYD"],"callingCode":["218"],"region":"Africa","subregion":"Northern Africa","flag":"flag-ly","name":{"common":"Libya","ces":"Libye","deu":"Libyen","fra":"Libye","hrv":"Libija","ita":"Libia","jpn":"リビア","nld":"Libië","por":"Líbia","rus":"Ливия","slk":"Líbya","spa":"Libia","fin":"Libya","est":"Liibüa","zho":"利比亚","pol":"Libia","urd":"لیبیا","kor":"리비아"}},"LI":{"currency":["CHF"],"callingCode":["423"],"region":"Europe","subregion":"Western Europe","flag":"flag-li","name":{"common":"Liechtenstein","ces":"Lichtenštejnsko","deu":"Liechtenstein","fra":"Liechtenstein","hrv":"Lihtenštajn","ita":"Liechtenstein","jpn":"リヒテンシュタイン","nld":"Liechtenstein","por":"Liechtenstein","rus":"Лихтенштейн","slk":"Lichtenštajnsko","spa":"Liechtenstein","fin":"Liechenstein","est":"Liechtenstein","zho":"列支敦士登","pol":"Liechtenstein","urd":"لیختینستائن","kor":"리히텐슈타인"}},"LT":{"currency":["EUR"],"callingCode":["370"],"region":"Europe","subregion":"Northern Europe","flag":"flag-lt","name":{"common":"Lithuania","ces":"Litva","deu":"Litauen","fra":"Lituanie","hrv":"Litva","ita":"Lituania","jpn":"リトアニア","nld":"Litouwen","por":"Lituânia","rus":"Литва","slk":"Litva","spa":"Lituania","fin":"Liettua","est":"Leedu","zho":"立陶宛","pol":"Litwa","urd":"لتھووینیا","kor":"리투아니아"}},"LU":{"currency":["EUR"],"callingCode":["352"],"region":"Europe","subregion":"Western Europe","flag":"flag-lu","name":{"common":"Luxembourg","ces":"Lucembursko","deu":"Luxemburg","fra":"Luxembourg","hrv":"Luksemburg","ita":"Lussemburgo","jpn":"ルクセンブルク","nld":"Luxemburg","por":"Luxemburgo","rus":"Люксембург","slk":"Luxembursko","spa":"Luxemburgo","fin":"Luxemburg","est":"Luksemburg","zho":"卢森堡","pol":"Luksemburg","urd":"لکسمبرگ","kor":"룩셈부르크"}},"MO":{"currency":["MOP"],"callingCode":["853"],"region":"Asia","subregion":"Eastern Asia","flag":"flag-mo","name":{"common":"Macau","ces":"Macao","deu":"Macao","fra":"Macao","hrv":"Makao","ita":"Macao","jpn":"マカオ","nld":"Macao","por":"Macau","rus":"Макао","slk":"Macao","spa":"Macao","fin":"Macao","est":"Macau","pol":"Makau","urd":"مکاؤ","kor":"마카오"}},"MK":{"currency":["MKD"],"callingCode":["389"],"region":"Europe","subregion":"Southern Europe","flag":"flag-mk","name":{"common":"Macedonia","ces":"Makedonie","deu":"Mazedonien","fra":"Macédoine","hrv":"Makedonija","ita":"Macedonia","jpn":"マケドニア旧ユーゴスラビア共和国","nld":"Macedonië","por":"Macedónia","rus":"Республика Македония","slk":"Macedónsko","spa":"Macedonia","fin":"Makedonia","est":"Makedoonia","zho":"马其顿","pol":"Macedonia","urd":"مقدونیہ","kor":"마케도니아"}},"MG":{"currency":["MGA"],"callingCode":["261"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-mg","name":{"common":"Madagascar","ces":"Madagaskar","deu":"Madagaskar","fra":"Madagascar","hrv":"Madagaskar","ita":"Madagascar","jpn":"マダガスカル","nld":"Madagaskar","por":"Madagáscar","rus":"Мадагаскар","slk":"Madagaskar","spa":"Madagascar","fin":"Madagaskar","est":"Madagaskar","zho":"马达加斯加","pol":"Madagaskar","urd":"مڈغاسکر","kor":"마다가스카르"}},"MW":{"currency":["MWK"],"callingCode":["265"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-mw","name":{"common":"Malawi","ces":"Malawi","deu":"Malawi","fra":"Malawi","hrv":"Malavi","ita":"Malawi","jpn":"マラウイ","nld":"Malawi","por":"Malawi","rus":"Малави","slk":"Malawi","spa":"Malawi","fin":"Malawi","est":"Malawi","zho":"马拉维","pol":"Malawi","urd":"ملاوی","kor":"말라위"}},"MY":{"currency":["MYR"],"callingCode":["60"],"region":"Asia","subregion":"South-Eastern Asia","flag":"flag-my","name":{"common":"Malaysia","ces":"Malajsie","deu":"Malaysia","fra":"Malaisie","hrv":"Malezija","ita":"Malesia","jpn":"マレーシア","nld":"Maleisië","por":"Malásia","rus":"Малайзия","slk":"Malajzia","spa":"Malasia","fin":"Malesia","est":"Malaisia","zho":"马来西亚","pol":"Malezja","urd":"ملائیشیا","kor":"말레이시아"}},"MV":{"currency":["MVR"],"callingCode":["960"],"region":"Asia","subregion":"Southern Asia","flag":"flag-mv","name":{"common":"Maldives","ces":"Maledivy","deu":"Malediven","fra":"Maldives","hrv":"Maldivi","ita":"Maldive","jpn":"モルディブ","nld":"Maldiven","por":"Maldivas","spa":"Maldivas","rus":"Мальдивы","slk":"Maldivy","fin":"Malediivit","est":"Maldiivid","zho":"马尔代夫","pol":"Malediwy","urd":"مالدیپ","kor":"몰디브"}},"ML":{"currency":["XOF"],"callingCode":["223"],"region":"Africa","subregion":"Western Africa","flag":"flag-ml","name":{"common":"Mali","ces":"Mali","deu":"Mali","fra":"Mali","hrv":"Mali","ita":"Mali","jpn":"マリ","nld":"Mali","por":"Mali","rus":"Мали","slk":"Mali","spa":"Mali","fin":"Mali","est":"Mali","zho":"马里","pol":"Mali","urd":"مالی","kor":"말리"}},"MT":{"currency":["EUR"],"callingCode":["356"],"region":"Europe","subregion":"Southern Europe","flag":"flag-mt","name":{"common":"Malta","ces":"Malta","deu":"Malta","fra":"Malte","hrv":"Malta","ita":"Malta","jpn":"マルタ","nld":"Malta","por":"Malta","rus":"Мальта","slk":"Malta","spa":"Malta","fin":"Malta","est":"Malta","zho":"马耳他","pol":"Malta","urd":"مالٹا","kor":"몰타"}},"MH":{"currency":["USD"],"callingCode":["692"],"region":"Oceania","subregion":"Micronesia","flag":"flag-mh","name":{"common":"Marshall Islands","ces":"Marshallovy ostrovy","deu":"Marshallinseln","fra":"Îles Marshall","hrv":"Maršalovi Otoci","ita":"Isole Marshall","jpn":"マーシャル諸島","nld":"Marshalleilanden","por":"Ilhas Marshall","rus":"Маршалловы Острова","slk":"Marshallove ostrovy","spa":"Islas Marshall","fin":"Marshallinsaaret","est":"Marshalli Saared","zho":"马绍尔群岛","pol":"Wyspy Marshalla","urd":"جزائر مارشل","kor":"마셜 제도"}},"MQ":{"currency":["EUR"],"callingCode":["596"],"region":"Americas","subregion":"Caribbean","flag":"flag-mq","name":{"common":"Martinique","ces":"Martinik","deu":"Martinique","fra":"Martinique","hrv":"Martinique","ita":"Martinica","jpn":"マルティニーク","nld":"Martinique","por":"Martinica","rus":"Мартиника","spa":"Martinica","slk":"Martinique","fin":"Martinique","est":"Martinique","zho":"马提尼克","pol":"Martynika","urd":"مارٹینیک","kor":"마르티니크"}},"MR":{"currency":["MRO"],"callingCode":["222"],"region":"Africa","subregion":"Western Africa","flag":"flag-mr","name":{"common":"Mauritania","ces":"Mauritánie","deu":"Mauretanien","fra":"Mauritanie","hrv":"Mauritanija","ita":"Mauritania","jpn":"モーリタニア","nld":"Mauritanië","por":"Mauritânia","rus":"Мавритания","slk":"Mauritánia","spa":"Mauritania","fin":"Mauritania","est":"Mauritaania","zho":"毛里塔尼亚","pol":"Mauretania","urd":"موریتانیہ","kor":"모리타니"}},"MU":{"currency":["MUR"],"callingCode":["230"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-mu","name":{"common":"Mauritius","ces":"Mauricius","deu":"Mauritius","fra":"Île Maurice","hrv":"Mauricijus","ita":"Mauritius","jpn":"モーリシャス","nld":"Mauritius","por":"Maurício","rus":"Маврикий","slk":"Maurícius","spa":"Mauricio","fin":"Mauritius","est":"Mauritius","zho":"毛里求斯","pol":"Mauritius","urd":"موریشس","kor":"모리셔스"}},"YT":{"currency":["EUR"],"callingCode":["262"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-yt","name":{"common":"Mayotte","ces":"Mayotte","deu":"Mayotte","fra":"Mayotte","hrv":"Mayotte","ita":"Mayotte","jpn":"マヨット","nld":"Mayotte","por":"Mayotte","rus":"Майотта","slk":"Mayotte","spa":"Mayotte","fin":"Mayotte","est":"Mayotte","zho":"马约特","pol":"Majotta","urd":"مایوٹ","kor":"마요트"}},"MX":{"currency":["MXN"],"callingCode":["52"],"region":"Americas","subregion":"North America","flag":"flag-mx","name":{"common":"Mexico","ces":"Mexiko","deu":"Mexiko","fra":"Mexique","hrv":"Meksiko","ita":"Messico","jpn":"メキシコ","nld":"Mexico","por":"México","rus":"Мексика","slk":"Mexiko","spa":"México","fin":"Meksiko","est":"Mehhiko","zho":"墨西哥","pol":"Meksyk","urd":"میکسیکو","kor":"멕시코"}},"FM":{"currency":["USD"],"callingCode":["691"],"region":"Oceania","subregion":"Micronesia","flag":"flag-fm","name":{"common":"Micronesia","ces":"Mikronésie","deu":"Mikronesien","fra":"Micronésie","hrv":"Mikronezija","ita":"Micronesia","jpn":"ミクロネシア連邦","nld":"Micronesië","por":"Micronésia","rus":"Федеративные Штаты Микронезии","slk":"Mikronézia","spa":"Micronesia","fin":"Mikronesia","est":"Mikroneesia","zho":"密克罗尼西亚","pol":"Mikronezja","urd":"مائکرونیشیا","kor":"미크로네시아"}},"MD":{"currency":["MDL"],"callingCode":["373"],"region":"Europe","subregion":"Eastern Europe","flag":"flag-md","name":{"common":"Moldova","ces":"Moldavsko","deu":"Moldawien","fra":"Moldavie","hrv":"Moldova","ita":"Moldavia","jpn":"モルドバ共和国","nld":"Moldavië","por":"Moldávia","rus":"Молдавия","slk":"Moldavsko","spa":"Moldavia","fin":"Moldova","est":"Moldova","zho":"摩尔多瓦","pol":"Mołdawia","urd":"مالدووا","kor":"몰도바"}},"MC":{"currency":["EUR"],"callingCode":["377"],"region":"Europe","subregion":"Western Europe","flag":"flag-mc","name":{"common":"Monaco","ces":"Monako","deu":"Monaco","fra":"Monaco","hrv":"Monako","ita":"Principato di Monaco","jpn":"モナコ","nld":"Monaco","por":"Mónaco","rus":"Монако","slk":"Monako","spa":"Mónaco","fin":"Monaco","est":"Monaco","zho":"摩纳哥","pol":"Monako","urd":"موناکو","kor":"모나코"}},"MN":{"currency":["MNT"],"callingCode":["976"],"region":"Asia","subregion":"Eastern Asia","flag":"flag-mn","name":{"common":"Mongolia","ces":"Mongolsko","deu":"Mongolei","fra":"Mongolie","hrv":"Mongolija","ita":"Mongolia","jpn":"モンゴル","nld":"Mongolië","por":"Mongólia","rus":"Монголия","slk":"Mongolsko","spa":"Mongolia","fin":"Mongolia","est":"Mongoolia","zho":"蒙古","pol":"Mongolia","urd":"منگولیا","kor":"몽골국"}},"ME":{"currency":["EUR"],"callingCode":["382"],"region":"Europe","subregion":"Southern Europe","flag":"flag-me","name":{"common":"Montenegro","ces":"Černá Hora","deu":"Montenegro","fra":"Monténégro","hrv":"Crna Gora","ita":"Montenegro","jpn":"モンテネグロ","nld":"Montenegro","por":"Montenegro","rus":"Черногория","slk":"Čierna Hora","spa":"Montenegro","fin":"Montenegro","est":"Montenegro","zho":"黑山","pol":"Czarnogóra","urd":"مونٹینیگرو","kor":"몬테네그로"}},"MS":{"currency":["XCD"],"callingCode":["1664"],"region":"Americas","subregion":"Caribbean","flag":"flag-ms","name":{"common":"Montserrat","ces":"Montserrat","deu":"Montserrat","fra":"Montserrat","hrv":"Montserrat","ita":"Montserrat","jpn":"モントセラト","nld":"Montserrat","por":"Montserrat","rus":"Монтсеррат","slk":"Montserrat","spa":"Montserrat","fin":"Montserrat","est":"Montserrat","zho":"蒙特塞拉特","pol":"Montserrat","urd":"مانٹسریٹ","kor":"몬트세랫"}},"MA":{"currency":["MAD"],"callingCode":["212"],"region":"Africa","subregion":"Northern Africa","flag":"flag-ma","name":{"common":"Morocco","ces":"Maroko","deu":"Marokko","fra":"Maroc","hrv":"Maroko","ita":"Marocco","jpn":"モロッコ","nld":"Marokko","por":"Marrocos","rus":"Марокко","slk":"Maroko","spa":"Marruecos","fin":"Marokko","est":"Maroko","zho":"摩洛哥","pol":"Maroko","urd":"مراکش","kor":"모로코"}},"MZ":{"currency":["MZN"],"callingCode":["258"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-mz","name":{"common":"Mozambique","ces":"Mosambik","deu":"Mosambik","fra":"Mozambique","hrv":"Mozambik","ita":"Mozambico","jpn":"モザンビーク","nld":"Mozambique","por":"Moçambique","rus":"Мозамбик","slk":"Mozambik","spa":"Mozambique","fin":"Mosambik","est":"Mosambiik","zho":"莫桑比克","pol":"Mozambik","urd":"موزمبیق","kor":"모잠비크"}},"MM":{"currency":["MMK"],"callingCode":["95"],"region":"Asia","subregion":"South-Eastern Asia","flag":"flag-mm","name":{"common":"Myanmar","ces":"Myanmar","deu":"Myanmar","fra":"Birmanie","hrv":"Mijanmar","ita":"Birmania","jpn":"ミャンマー","nld":"Myanmar","por":"Myanmar","rus":"Мьянма","slk":"Mjanmarsko","spa":"Myanmar","fin":"Myanmar","est":"Myanmar","zho":"缅甸","pol":"Mjanma","urd":"میانمار","kor":"미얀마"}},"NA":{"currency":["NAD","ZAR"],"callingCode":["264"],"region":"Africa","subregion":"Southern Africa","flag":"flag-na","name":{"common":"Namibia","ces":"Namibie","deu":"Namibia","fra":"Namibie","hrv":"Namibija","ita":"Namibia","jpn":"ナミビア","nld":"Namibië","por":"Namíbia","rus":"Намибия","slk":"Namíbia","spa":"Namibia","fin":"Namibia","est":"Namiibia","zho":"纳米比亚","pol":"Namibia","urd":"نمیبیا","kor":"나미비아"}},"NR":{"currency":["AUD"],"callingCode":["674"],"region":"Oceania","subregion":"Micronesia","flag":"flag-nr","name":{"common":"Nauru","ces":"Nauru","deu":"Nauru","fra":"Nauru","hrv":"Nauru","ita":"Nauru","jpn":"ナウル","nld":"Nauru","por":"Nauru","rus":"Науру","slk":"Nauru","spa":"Nauru","fin":"Nauru","est":"Nauru","zho":"瑙鲁","pol":"Nauru","urd":"ناورو","kor":"나우루"}},"NP":{"currency":["NPR"],"callingCode":["977"],"region":"Asia","subregion":"Southern Asia","flag":"flag-np","name":{"common":"Nepal","ces":"Nepál","deu":"Nepal","fra":"Népal","hrv":"Nepal","ita":"Nepal","jpn":"ネパール","nld":"Nepal","por":"Nepal","rus":"Непал","slk":"Nepál","spa":"Nepal","fin":"Nepal","est":"Nepal","zho":"尼泊尔","pol":"Nepal","urd":"نیپال","kor":"네팔"}},"NL":{"currency":["EUR"],"callingCode":["31"],"region":"Europe","subregion":"Western Europe","flag":"flag-nl","name":{"common":"Netherlands","ces":"Nizozemsko","deu":"Niederlande","fra":"Pays-Bas","hrv":"Nizozemska","ita":"Paesi Bassi","jpn":"オランダ","nld":"Nederland","por":"Holanda","rus":"Нидерланды","slk":"Holansko","spa":"Países Bajos","fin":"Alankomaat","est":"Holland","zho":"荷兰","pol":"Holandia","urd":"نیدرلینڈز","kor":"네덜란드"}},"NC":{"currency":["XPF"],"callingCode":["687"],"region":"Oceania","subregion":"Melanesia","flag":"flag-nc","name":{"common":"New Caledonia","ces":"Nová Kaledonie","deu":"Neukaledonien","fra":"Nouvelle-Calédonie","hrv":"Nova Kaledonija","ita":"Nuova Caledonia","jpn":"ニューカレドニア","nld":"Nieuw-Caledonië","por":"Nova Caledónia","rus":"Новая Каледония","slk":"Nová Kaledónia","spa":"Nueva Caledonia","fin":"Uusi-Kaledonia","est":"Uus-Kaledoonia","zho":"新喀里多尼亚","pol":"Nowa Kaledonia","urd":"نیو کیلیڈونیا","kor":"누벨칼레도니"}},"NZ":{"currency":["NZD"],"callingCode":["64"],"region":"Oceania","subregion":"Australia and New Zealand","flag":"flag-nz","name":{"common":"New Zealand","ces":"Nový Zéland","deu":"Neuseeland","fra":"Nouvelle-Zélande","hrv":"Novi Zeland","ita":"Nuova Zelanda","jpn":"ニュージーランド","nld":"Nieuw-Zeeland","por":"Nova Zelândia","rus":"Новая Зеландия","slk":"Nový Zéland","spa":"Nueva Zelanda","fin":"Uusi-Seelanti","est":"Uus-Meremaa","zho":"新西兰","pol":"Nowa Zelandia","urd":"نیوزی لینڈ","kor":"뉴질랜드"}},"NI":{"currency":["NIO"],"callingCode":["505"],"region":"Americas","subregion":"Central America","flag":"flag-ni","name":{"common":"Nicaragua","ces":"Nikaragua","deu":"Nicaragua","fra":"Nicaragua","hrv":"Nikaragva","ita":"Nicaragua","jpn":"ニカラグア","nld":"Nicaragua","por":"Nicarágua","rus":"Никарагуа","slk":"Nikaragua","spa":"Nicaragua","fin":"Nicaragua","est":"Nicaragua","zho":"尼加拉瓜","pol":"Nikaragua","urd":"نکاراگوا","kor":"니카라과"}},"NE":{"currency":["XOF"],"callingCode":["227"],"region":"Africa","subregion":"Western Africa","flag":"flag-ne","name":{"common":"Niger","ces":"Niger","deu":"Niger","fra":"Niger","hrv":"Niger","ita":"Niger","jpn":"ニジェール","nld":"Niger","por":"Níger","rus":"Нигер","slk":"Niger","spa":"Níger","fin":"Niger","est":"Niger","zho":"尼日尔","pol":"Niger","urd":"نائجر","kor":"니제르"}},"NG":{"currency":["NGN"],"callingCode":["234"],"region":"Africa","subregion":"Western Africa","flag":"flag-ng","name":{"common":"Nigeria","ces":"Nigérie","deu":"Nigeria","fra":"Nigéria","hrv":"Nigerija","ita":"Nigeria","jpn":"ナイジェリア","nld":"Nigeria","por":"Nigéria","rus":"Нигерия","slk":"Nigéria","spa":"Nigeria","fin":"Nigeria","est":"Nigeeria","zho":"尼日利亚","pol":"Nigeria","urd":"نائجیریا","kor":"나이지리아"}},"NU":{"currency":["NZD"],"callingCode":["683"],"region":"Oceania","subregion":"Polynesia","flag":"flag-nu","name":{"common":"Niue","ces":"Niue","deu":"Niue","fra":"Niue","hrv":"Niue","ita":"Niue","jpn":"ニウエ","nld":"Niue","por":"Niue","rus":"Ниуэ","slk":"Niue","spa":"Niue","fin":"Niue","est":"Niue","zho":"纽埃","pol":"Niue","urd":"نیووے","kor":"니우에"}},"NF":{"currency":["AUD"],"callingCode":["672"],"region":"Oceania","subregion":"Australia and New Zealand","flag":"flag-nf","name":{"common":"Norfolk Island","ces":"Norfolk","deu":"Norfolkinsel","fra":"Île Norfolk","hrv":"Otok Norfolk","ita":"Isola Norfolk","jpn":"ノーフォーク島","nld":"Norfolkeiland","por":"Ilha Norfolk","rus":"Норфолк","slk":"Norfolk","spa":"Isla de Norfolk","fin":"Norfolkinsaari","est":"Norfolk","zho":"诺福克岛","pol":"Wyspa Norfolk","urd":"جزیرہ نورفک","kor":"노퍽 섬"}},"KP":{"currency":["KPW"],"callingCode":["850"],"region":"Asia","subregion":"Eastern Asia","flag":"flag-kp","name":{"common":"North Korea","ces":"Severní Korea","deu":"Nordkorea","fra":"Corée du Nord","hrv":"Sjeverna Koreja","ita":"Corea del Nord","jpn":"朝鮮民主主義人民共和国","nld":"Noord-Korea","por":"Coreia do Norte","rus":"Северная Корея","slk":"Kórejská ľudovodemokratická republika (KĽR, Severná Kórea)","spa":"Corea del Norte","fin":"Pohjois-Korea","est":"Põhja-Korea","zho":"朝鲜","pol":"Korea Północna","urd":"شمالی کوریا","kor":"조선"}},"MP":{"currency":["USD"],"callingCode":["1670"],"region":"Oceania","subregion":"Micronesia","flag":"flag-mp","name":{"common":"Northern Mariana Islands","ces":"Severní Mariany","deu":"Nördliche Marianen","fra":"Îles Mariannes du Nord","hrv":"Sjevernomarijanski otoci","ita":"Isole Marianne Settentrionali","jpn":"北マリアナ諸島","nld":"Noordelijke Marianeneilanden","por":"Marianas Setentrionais","rus":"Северные Марианские острова","slk":"Severné Mariány","spa":"Islas Marianas del Norte","fin":"Pohjois-Mariaanit","est":"Põhja-Mariaanid","zho":"北马里亚纳群岛","pol":"Mariany Północne","urd":"جزائر شمالی ماریانا","kor":"북마리아나 제도"}},"NO":{"currency":["NOK"],"callingCode":["47"],"region":"Europe","subregion":"Northern Europe","flag":"flag-no","name":{"common":"Norway","ces":"Norsko","deu":"Norwegen","fra":"Norvège","hrv":"Norveška","ita":"Norvegia","jpn":"ノルウェー","nld":"Noorwegen","por":"Noruega","rus":"Норвегия","slk":"Nórsko","spa":"Noruega","fin":"Norja","est":"Norra","zho":"挪威","pol":"Norwegia","urd":"ناروے","kor":"노르웨이"}},"OM":{"currency":["OMR"],"callingCode":["968"],"region":"Asia","subregion":"Western Asia","flag":"flag-om","name":{"common":"Oman","ces":"Omán","deu":"Oman","fra":"Oman","hrv":"Oman","ita":"oman","jpn":"オマーン","nld":"Oman","por":"Omã","rus":"Оман","slk":"Omán","spa":"Omán","fin":"Oman","est":"Omaan","zho":"阿曼","pol":"Oman","urd":"عمان","kor":"오만"}},"PK":{"currency":["PKR"],"callingCode":["92"],"region":"Asia","subregion":"Southern Asia","flag":"flag-pk","name":{"common":"Pakistan","ces":"Pákistán","deu":"Pakistan","fra":"Pakistan","hrv":"Pakistan","ita":"Pakistan","jpn":"パキスタン","nld":"Pakistan","por":"Paquistão","rus":"Пакистан","slk":"Pakistan","spa":"Pakistán","fin":"Pakistan","est":"Pakistan","zho":"巴基斯坦","pol":"Pakistan","urd":"پاکستان","kor":"파키스탄"}},"PW":{"currency":["USD"],"callingCode":["680"],"region":"Oceania","subregion":"Micronesia","flag":"flag-pw","name":{"common":"Palau","ces":"Palau","deu":"Palau","fra":"Palaos (Palau)","hrv":"Palau","ita":"Palau","jpn":"パラオ","nld":"Palau","por":"Palau","rus":"Палау","slk":"Palau","spa":"Palau","fin":"Palau","est":"Belau","zho":"帕劳","pol":"Palau","urd":"پلاؤ","kor":"팔라우"}},"PS":{"currency":["ILS"],"callingCode":["970"],"region":"Asia","subregion":"Western Asia","flag":"flag-ps","name":{"common":"Palestine","ces":"Palestina","deu":"Palästina","fra":"Palestine","hrv":"Palestina","ita":"Palestina","jpn":"パレスチナ","nld":"Palestijnse gebieden","por":"Palestina","rus":"Палестина","slk":"Palestína","spa":"Palestina","fin":"Palestiina","est":"Palestiina","zho":"巴勒斯坦","pol":"Palestyna","urd":"فلسطین","kor":"팔레스타인"}},"PA":{"currency":["PAB","USD"],"callingCode":["507"],"region":"Americas","subregion":"Central America","flag":"flag-pa","name":{"common":"Panama","ces":"Panama","deu":"Panama","fra":"Panama","hrv":"Panama","ita":"Panama","jpn":"パナマ","nld":"Panama","por":"Panamá","rus":"Панама","slk":"Panama","spa":"Panamá","fin":"Panama","est":"Panama","zho":"巴拿马","pol":"Panama","urd":"پاناما","kor":"파나마"}},"PG":{"currency":["PGK"],"callingCode":["675"],"region":"Oceania","subregion":"Melanesia","flag":"flag-pg","name":{"common":"Papua New Guinea","ces":"Papua-Nová Guinea","deu":"Papua-Neuguinea","fra":"Papouasie-Nouvelle-Guinée","hrv":"Papua Nova Gvineja","ita":"Papua Nuova Guinea","jpn":"パプアニューギニア","nld":"Papoea-Nieuw-Guinea","por":"Papua Nova Guiné","rus":"Папуа — Новая Гвинея","slk":"Papua-Nová Guinea","spa":"Papúa Nueva Guinea","fin":"Papua-Uusi-Guinea","est":"Paapua Uus-Guinea","zho":"巴布亚新几内亚","pol":"Papua-Nowa Gwinea","urd":"پاپوا نیو گنی","kor":"파푸아뉴기니"}},"PY":{"currency":["PYG"],"callingCode":["595"],"region":"Americas","subregion":"South America","flag":"flag-py","name":{"common":"Paraguay","ces":"Paraguay","deu":"Paraguay","fra":"Paraguay","hrv":"Paragvaj","ita":"Paraguay","jpn":"パラグアイ","nld":"Paraguay","por":"Paraguai","rus":"Парагвай","slk":"Paraguaj","spa":"Paraguay","fin":"Paraguay","est":"Paraguay","zho":"巴拉圭","pol":"Paragwaj","urd":"پیراگوئے","kor":"파라과이"}},"PE":{"currency":["PEN"],"callingCode":["51"],"region":"Americas","subregion":"South America","flag":"flag-pe","name":{"common":"Peru","ces":"Peru","deu":"Peru","fra":"Pérou","hrv":"Peru","ita":"Perù","jpn":"ペルー","nld":"Peru","por":"Perú","rus":"Перу","slk":"Peru","spa":"Perú","fin":"Peru","est":"Peruu","zho":"秘鲁","pol":"Peru","urd":"پیرو","kor":"페루"}},"PH":{"currency":["PHP"],"callingCode":["63"],"region":"Asia","subregion":"South-Eastern Asia","flag":"flag-ph","name":{"common":"Philippines","ces":"Filipíny","deu":"Philippinen","fra":"Philippines","hrv":"Filipini","ita":"Filippine","jpn":"フィリピン","nld":"Filipijnen","por":"Filipinas","rus":"Филиппины","slk":"Filipíny","spa":"Filipinas","fin":"Filippiinit","est":"Filipiinid","zho":"菲律宾","pol":"Filipiny","urd":"فلپائن","kor":"필리핀"}},"PN":{"currency":["NZD"],"callingCode":["64"],"region":"Oceania","subregion":"Polynesia","flag":"flag-pn","name":{"common":"Pitcairn Islands","ces":"Pitcairnovy ostrovy","deu":"Pitcairninseln","fra":"Îles Pitcairn","hrv":"Pitcairnovo otočje","ita":"Isole Pitcairn","jpn":"ピトケアン","nld":"Pitcairneilanden","por":"Ilhas Pitcairn","rus":"Острова Питкэрн","slk":"Pitcairnove ostrovy","spa":"Islas Pitcairn","fin":"Pitcairn","est":"Pitcairn","zho":"皮特凯恩群岛","pol":"Pitcairn","urd":"جزائر پٹکیرن","kor":"핏케언 제도"}},"PL":{"currency":["PLN"],"callingCode":["48"],"region":"Europe","subregion":"Eastern Europe","flag":"flag-pl","name":{"common":"Poland","ces":"Polsko","deu":"Polen","fra":"Pologne","hrv":"Poljska","ita":"Polonia","jpn":"ポーランド","nld":"Polen","por":"Polónia","rus":"Польша","slk":"Poľsko","spa":"Polonia","fin":"Puola","est":"Poola","zho":"波兰","pol":"Polska","urd":"پولینڈ","kor":"폴란드"}},"PT":{"currency":["EUR"],"callingCode":["351"],"region":"Europe","subregion":"Southern Europe","flag":"flag-pt","name":{"common":"Portugal","ces":"Portugalsko","deu":"Portugal","fra":"Portugal","hrv":"Portugal","ita":"Portogallo","jpn":"ポルトガル","nld":"Portugal","por":"Portugal","rus":"Португалия","slk":"Portugalsko","spa":"Portugal","fin":"Portugali","est":"Portugal","zho":"葡萄牙","pol":"Portugalia","urd":"پرتگال","kor":"포르투갈"}},"PR":{"currency":["USD"],"callingCode":["1787","1939"],"region":"Americas","subregion":"Caribbean","flag":"flag-pr","name":{"common":"Puerto Rico","ces":"Portoriko","deu":"Puerto Rico","fra":"Porto Rico","hrv":"Portoriko","ita":"Porto Rico","jpn":"プエルトリコ","nld":"Puerto Rico","por":"Porto Rico","rus":"Пуэрто-Рико","slk":"Portoriko","spa":"Puerto Rico","fin":"Puerto Rico","est":"Puerto Rico","zho":"波多黎各","pol":"Portoryko","urd":"پورٹو ریکو","kor":"푸에르토리코"}},"QA":{"currency":["QAR"],"callingCode":["974"],"region":"Asia","subregion":"Western Asia","flag":"flag-qa","name":{"common":"Qatar","ces":"Katar","deu":"Katar","fra":"Qatar","hrv":"Katar","ita":"Qatar","jpn":"カタール","nld":"Qatar","por":"Catar","rus":"Катар","slk":"Katar","spa":"Catar","fin":"Qatar","est":"Katar","zho":"卡塔尔","pol":"Katar","urd":"قطر","kor":"카타르"}},"CG":{"currency":["XAF"],"callingCode":["242"],"region":"Africa","subregion":"Middle Africa","flag":"flag-cg","name":{"common":"Republic of the Congo","ces":"Kongo","cym":"Gweriniaeth y Congo","deu":"Kongo","fra":"Congo","hrv":"Kongo","ita":"Congo","jpn":"コンゴ共和国","nld":"Congo","por":"Congo","rus":"Республика Конго","slk":"Kongo","spa":"Congo","fin":"Kongo-Brazzaville","est":"Kongo Vabariik","zho":"刚果","pol":"Kongo","urd":"جمہوریہ کانگو","kor":"콩고"}},"RO":{"currency":["RON"],"callingCode":["40"],"region":"Europe","subregion":"Eastern Europe","flag":"flag-ro","name":{"common":"Romania","ces":"Rumunsko","deu":"Rumänien","fra":"Roumanie","hrv":"Rumunjska","ita":"Romania","jpn":"ルーマニア","nld":"Roemenië","por":"Roménia","rus":"Румыния","slk":"Rumunsko","spa":"Rumania","fin":"Romania","est":"Rumeenia","zho":"罗马尼亚","pol":"Rumunia","urd":"رومانیہ","kor":"루마니아"}},"RU":{"currency":["RUB"],"callingCode":["7"],"region":"Europe","subregion":"Eastern Europe","flag":"flag-ru","name":{"common":"Russia","ces":"Rusko","deu":"Russland","fra":"Russie","hrv":"Rusija","ita":"Russia","jpn":"ロシア連邦","nld":"Rusland","por":"Rússia","rus":"Россия","slk":"Rusko","spa":"Rusia","fin":"Venäjä","est":"Venemaa","zho":"俄罗斯","pol":"Rosja","urd":"روس","kor":"러시아"}},"RW":{"currency":["RWF"],"callingCode":["250"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-rw","name":{"common":"Rwanda","ces":"Rwanda","deu":"Ruanda","fra":"Rwanda","hrv":"Ruanda","ita":"Ruanda","jpn":"ルワンダ","nld":"Rwanda","por":"Ruanda","rus":"Руанда","slk":"Rwanda","spa":"Ruanda","fin":"Ruanda","est":"Rwanda","zho":"卢旺达","pol":"Rwanda","urd":"روانڈا","kor":"르완다"}},"RE":{"currency":["EUR"],"callingCode":["262"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-re","name":{"common":"Réunion","ces":"Réunion","deu":"Réunion","fra":"Réunion","hrv":"Réunion","ita":"Riunione","jpn":"レユニオン","nld":"Réunion","por":"Reunião","rus":"Реюньон","slk":"Réunion","spa":"Reunión","fin":"Réunion","est":"Réunion","zho":"留尼旺岛","pol":"Reunion","urd":"رے یونیوں","kor":"레위니옹"}},"BL":{"currency":["EUR"],"callingCode":["590"],"region":"Americas","subregion":"Caribbean","flag":"flag-bl","name":{"common":"Saint Barthélemy","ces":"Svatý Bartoloměj","deu":"Saint-Barthélemy","fra":"Saint-Barthélemy","hrv":"Saint Barthélemy","ita":"Antille Francesi","jpn":"サン・バルテルミー","nld":"Saint Barthélemy","por":"São Bartolomeu","rus":"Сен-Бартелеми","slk":"Svätý Bartolomej","spa":"San Bartolomé","fin":"Saint-Barthélemy","est":"Saint-Barthélemy","zho":"圣巴泰勒米","pol":"Saint-Barthélemy","urd":"سینٹ بارتھیملے","kor":"생바르텔레미"}},"SH":{"currency":["SHP","GBP"],"callingCode":["290","247"],"region":"Africa","subregion":"Western Africa","flag":"flag-sh","name":{"common":"Saint Helena, Ascension and Tristan da Cunha","ces":"Svatá Helena, Ascension a Tristan da Cunha","deu":"St. Helena, Ascension und Tristan da Cunha","fra":"Sainte-Hélène, Ascension et Tristan da Cunha","hrv":"Sveta Helena","ita":"Sant'Elena, Ascensione e Tristan da Cunha","jpn":"セントヘレナ・アセンションおよびトリスタンダクーニャ","nld":"Sint-Helena, Ascension en Tristan da Cunha","por":"Santa Helena, Ascensão e Tristão da Cunha","rus":"Острова Святой Елены, Вознесения и Тристан-да-Кунья","slk":"Svätá Helena (zámorské územie)","spa":"Santa Elena, Ascensión y Tristán de Acuña","fin":"Saint Helena, Ascension ja Tristan da Cunha","est":"Saint Helena, Ascension ja Tristan da Cunha","zho":"圣赫勒拿、阿森松和特里斯坦-达库尼亚","pol":"Wyspa Świętej Heleny, Wyspa Wniebowstąpienia i Tristan da Cunha","urd":"سینٹ ہلینا، اسینشن و ترسٹان دا کونیا","kor":"세인트헬레나"}},"KN":{"currency":["XCD"],"callingCode":["1869"],"region":"Americas","subregion":"Caribbean","flag":"flag-kn","name":{"common":"Saint Kitts and Nevis","ces":"Svatý Kryštof a Nevis","deu":"St. Kitts und Nevis","fra":"Saint-Christophe-et-Niévès","hrv":"Sveti Kristof i Nevis","ita":"Saint Kitts e Nevis","jpn":"セントクリストファー・ネイビス","nld":"Saint Kitts en Nevis","por":"São Cristóvão e Nevis","rus":"Сент-Китс и Невис","slk":"Svätý Krištof a Nevis","spa":"San Cristóbal y Nieves","fin":"Saint Kitts ja Nevis","est":"Saint Kitts ja Nevis","zho":"圣基茨和尼维斯","pol":"Saint Kitts i Nevis","urd":"سینٹ کیٹز و ناویس","kor":"세인트키츠 네비스"}},"LC":{"currency":["XCD"],"callingCode":["1758"],"region":"Americas","subregion":"Caribbean","flag":"flag-lc","name":{"common":"Saint Lucia","ces":"Svatá Lucie","deu":"St. Lucia","fra":"Sainte-Lucie","hrv":"Sveta Lucija","ita":"Santa Lucia","jpn":"セントルシア","nld":"Saint Lucia","por":"Santa Lúcia","rus":"Сент-Люсия","slk":"Svätá Lucia","spa":"Santa Lucía","fin":"Saint Lucia","est":"Saint Lucia","zho":"圣卢西亚","pol":"Saint Lucia","urd":"سینٹ لوسیا","kor":"세인트루시아"}},"MF":{"currency":["EUR"],"callingCode":["590"],"region":"Americas","subregion":"Caribbean","flag":"flag-mf","name":{"common":"Saint Martin","ces":"Svatý Martin (Francie)","deu":"Saint-Martin","fra":"Saint-Martin","hrv":"Sveti Martin","ita":"Saint Martin","jpn":"サン・マルタン(フランス領)","nld":"Saint-Martin","por":"São Martinho","rus":"Сен-Мартен","slk":"Saint-Martin","spa":"Saint Martin","fin":"Saint-Martin","est":"Saint-Martin","zho":"圣马丁","pol":"Saint-Martin","urd":"سینٹ مارٹن","kor":"생마르탱"}},"PM":{"currency":["EUR"],"callingCode":["508"],"region":"Americas","subregion":"North America","flag":"flag-pm","name":{"common":"Saint Pierre and Miquelon","ces":"Saint-Pierre a Miquelon","deu":"St. Pierre und Miquelon","fra":"Saint-Pierre-et-Miquelon","hrv":"Sveti Petar i Mikelon","ita":"Saint-Pierre e Miquelon","jpn":"サンピエール島・ミクロン島","nld":"Saint Pierre en Miquelon","por":"Saint-Pierre e Miquelon","rus":"Сен-Пьер и Микелон","slk":"Saint Pierre a Miquelon","spa":"San Pedro y Miquelón","fin":"Saint-Pierre ja Miquelon","est":"Saint-Pierre ja Miquelon","zho":"圣皮埃尔和密克隆","pol":"Saint-Pierre i Miquelon","urd":"سینٹ پیئر و میکیلون","kor":"생피에르 미클롱"}},"VC":{"currency":["XCD"],"callingCode":["1784"],"region":"Americas","subregion":"Caribbean","flag":"flag-vc","name":{"common":"Saint Vincent and the Grenadines","ces":"Svatý Vincenc a Grenadiny","deu":"St. Vincent und die Grenadinen","fra":"Saint-Vincent-et-les-Grenadines","hrv":"Sveti Vincent i Grenadini","ita":"Saint Vincent e Grenadine","jpn":"セントビンセントおよびグレナディーン諸島","nld":"Saint Vincent en de Grenadines","por":"São Vincente e Granadinas","rus":"Сент-Винсент и Гренадины","slk":"Svätý Vincent a Grenadíny","spa":"San Vicente y Granadinas","fin":"Saint Vincent ja Grenadiinit","est":"Saint Vincent","zho":"圣文森特和格林纳丁斯","pol":"Saint Vincent i Grenadyny","urd":"سینٹ وینسینٹ و گریناڈائنز","kor":"세인트빈센트 그레나딘"}},"WS":{"currency":["WST"],"callingCode":["685"],"region":"Oceania","subregion":"Polynesia","flag":"flag-ws","name":{"common":"Samoa","ces":"Samoa","deu":"Samoa","fra":"Samoa","hrv":"Samoa","ita":"Samoa","jpn":"サモア","nld":"Samoa","por":"Samoa","rus":"Самоа","slk":"Samoa","spa":"Samoa","fin":"Samoa","est":"Samoa","zho":"萨摩亚","pol":"Samoa","urd":"سامووا","kor":"사모아"}},"SM":{"currency":["EUR"],"callingCode":["378"],"region":"Europe","subregion":"Southern Europe","flag":"flag-sm","name":{"common":"San Marino","ces":"San Marino","deu":"San Marino","fra":"Saint-Marin","hrv":"San Marino","ita":"San Marino","jpn":"サンマリノ","nld":"San Marino","por":"San Marino","rus":"Сан-Марино","slk":"San Maríno","spa":"San Marino","fin":"San Marino","est":"San Marino","zho":"圣马力诺","pol":"San Marino","urd":"سان مارینو","kor":"산마리노"}},"SA":{"currency":["SAR"],"callingCode":["966"],"region":"Asia","subregion":"Western Asia","flag":"flag-sa","name":{"common":"Saudi Arabia","ces":"Saúdská Arábie","deu":"Saudi-Arabien","fra":"Arabie Saoudite","hrv":"Saudijska Arabija","ita":"Arabia Saudita","jpn":"サウジアラビア","nld":"Saoedi-Arabië","por":"Arábia Saudita","rus":"Саудовская Аравия","slk":"Saudská Arábia","spa":"Arabia Saudí","fin":"Saudi-Arabia","est":"Saudi Araabia","zho":"沙特阿拉伯","pol":"Arabia Saudyjska","urd":"سعودی عرب","kor":"사우디아라비아"}},"SN":{"currency":["XOF"],"callingCode":["221"],"region":"Africa","subregion":"Western Africa","flag":"flag-sn","name":{"common":"Senegal","ces":"Senegal","deu":"Senegal","fra":"Sénégal","hrv":"Senegal","ita":"Senegal","jpn":"セネガル","nld":"Senegal","por":"Senegal","rus":"Сенегал","slk":"Senegal","spa":"Senegal","fin":"Senegal","est":"Senegal","zho":"塞内加尔","pol":"Senegal","urd":"سینیگال","kor":"세네갈"}},"RS":{"currency":["RSD"],"callingCode":["381"],"region":"Europe","subregion":"Southern Europe","flag":"flag-rs","name":{"common":"Serbia","ces":"Srbsko","deu":"Serbien","fra":"Serbie","hrv":"Srbija","ita":"Serbia","jpn":"セルビア","nld":"Servië","por":"Sérvia","rus":"Сербия","slk":"Srbsko","spa":"Serbia","fin":"Serbia","est":"Serbia","zho":"塞尔维亚","pol":"Serbia","urd":"سربیا","kor":"세르비아"}},"SC":{"currency":["SCR"],"callingCode":["248"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-sc","name":{"common":"Seychelles","ces":"Seychely","deu":"Seychellen","fra":"Seychelles","hrv":"Sejšeli","ita":"Seychelles","jpn":"セーシェル","nld":"Seychellen","por":"Seicheles","rus":"Сейшельские Острова","slk":"Seychely","spa":"Seychelles","fin":"Seychellit","est":"Seišellid","zho":"塞舌尔","pol":"Seszele","urd":"سیچیلیس","kor":"세이셸"}},"SL":{"currency":["SLL"],"callingCode":["232"],"region":"Africa","subregion":"Western Africa","flag":"flag-sl","name":{"common":"Sierra Leone","ces":"Sierra Leone","deu":"Sierra Leone","fra":"Sierra Leone","hrv":"Sijera Leone","ita":"Sierra Leone","jpn":"シエラレオネ","nld":"Sierra Leone","por":"Serra Leoa","rus":"Сьерра-Леоне","slk":"Sierra Leone","spa":"Sierra Leone","fin":"Sierra Leone","est":"Sierra Leone","zho":"塞拉利昂","pol":"Sierra Leone","urd":"سیرالیون","kor":"시에라리온"}},"SG":{"currency":["SGD"],"callingCode":["65"],"region":"Asia","subregion":"South-Eastern Asia","flag":"flag-sg","name":{"common":"Singapore","ces":"Singapur","deu":"Singapur","fra":"Singapour","hrv":"Singapur","ita":"Singapore","jpn":"シンガポール","nld":"Singapore","por":"Singapura","rus":"Сингапур","slk":"Singapur","spa":"Singapur","fin":"Singapore","est":"Singapur","pol":"Singapur","urd":"سنگاپور","kor":"싱가포르"}},"SX":{"currency":["ANG"],"callingCode":["1721"],"region":"Americas","subregion":"Caribbean","flag":"flag-sx","name":{"common":"Sint Maarten","ces":"Svatý Martin (Nizozemsko)","deu":"Sint Maarten","fra":"Saint-Martin","hrv":"Sveti Martin","ita":"Sint Maarten","jpn":"シント・マールテン","nld":"Sint Maarten","por":"São Martinho","rus":"Синт-Мартен","slk":"Sint Maarten","spa":"Sint Maarten","fin":"Sint Maarten","est":"Sint Maarten","zho":"圣马丁岛","pol":"Sint Maarten","urd":"سنٹ مارٹن","kor":"신트마르턴"}},"SK":{"currency":["EUR"],"callingCode":["421"],"region":"Europe","subregion":"Central Europe","flag":"flag-sk","name":{"common":"Slovakia","ces":"Slovensko","deu":"Slowakei","fra":"Slovaquie","hrv":"Slovačka","ita":"Slovacchia","jpn":"スロバキア","nld":"Slowakije","por":"Eslováquia","rus":"Словакия","slk":"Slovensko","spa":"República Eslovaca","fin":"Slovakia","est":"Slovakkia","zho":"斯洛伐克","pol":"Słowacja","urd":"سلوواکیہ","kor":"슬로바키아"}},"SI":{"currency":["EUR"],"callingCode":["386"],"region":"Europe","subregion":"Southern Europe","flag":"flag-si","name":{"common":"Slovenia","ces":"Slovinsko","deu":"Slowenien","fra":"Slovénie","hrv":"Slovenija","ita":"Slovenia","jpn":"スロベニア","nld":"Slovenië","por":"Eslovénia","rus":"Словения","slk":"Slovinsko","spa":"Eslovenia","fin":"Slovenia","est":"Sloveenia","zho":"斯洛文尼亚","pol":"Słowenia","urd":"سلووینیا","kor":"슬로베니아"}},"SB":{"currency":["SBD"],"callingCode":["677"],"region":"Oceania","subregion":"Melanesia","flag":"flag-sb","name":{"common":"Solomon Islands","ces":"Šalamounovy ostrovy","deu":"Salomonen","fra":"Îles Salomon","hrv":"Solomonski Otoci","ita":"Isole Salomone","jpn":"ソロモン諸島","nld":"Salomonseilanden","por":"Ilhas Salomão","rus":"Соломоновы Острова","slk":"Salomonove ostrovy","spa":"Islas Salomón","fin":"Salomonsaaret","est":"Saalomoni Saared","zho":"所罗门群岛","pol":"Wyspy Salomona","urd":"جزائر سلیمان","kor":"솔로몬 제도"}},"SO":{"currency":["SOS"],"callingCode":["252"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-so","name":{"common":"Somalia","ces":"Somálsko","deu":"Somalia","fra":"Somalie","hrv":"Somalija","ita":"Somalia","jpn":"ソマリア","nld":"Somalië","por":"Somália","rus":"Сомали","slk":"Somálsko","spa":"Somalia","fin":"Somalia","est":"Somaalia","zho":"索马里","pol":"Somalia","urd":"صومالیہ","kor":"소말리아"}},"ZA":{"currency":["ZAR"],"callingCode":["27"],"region":"Africa","subregion":"Southern Africa","flag":"flag-za","name":{"common":"South Africa","ces":"Jihoafrická republika","deu":"Südafrika","fra":"Afrique du Sud","hrv":"Južnoafrička Republika","ita":"Sud Africa","jpn":"南アフリカ","nld":"Zuid-Afrika","por":"África do Sul","rus":"Южно-Африканская Республика","slk":"Juhoafrická republika","spa":"República de Sudáfrica","fin":"Etelä-Afrikka","est":"Lõuna-Aafrika Vabariik","zho":"南非","pol":"Południowa Afryka","urd":"جنوبی افریقا","kor":"남아프리카"}},"GS":{"currency":["GBP"],"callingCode":["500"],"region":"Antarctic","subregion":"","flag":"flag-gs","name":{"common":"South Georgia","ces":"Jižní Georgie a Jižní Sandwichovy ostrovy","deu":"Südgeorgien und die Südlichen Sandwichinseln","fra":"Géorgie du Sud-et-les Îles Sandwich du Sud","hrv":"Južna Georgija i otočje Južni Sandwich","ita":"Georgia del Sud e Isole Sandwich Meridionali","jpn":"サウスジョージア・サウスサンドウィッチ諸島","nld":"Zuid-Georgia en Zuidelijke Sandwicheilanden","por":"Ilhas Geórgia do Sul e Sandwich do Sul","rus":"Южная Георгия и Южные Сандвичевы острова","slk":"Južná Georgia a Južné Sandwichove ostrovy","spa":"Islas Georgias del Sur y Sandwich del Sur","fin":"Etelä-Georgia ja Eteläiset Sandwichsaaret","est":"Lõuna-Georgia ja Lõuna-Sandwichi saared","zho":"南乔治亚","pol":"Georgia Południowa i Sandwich Południowy","urd":"جنوبی جارجیا","kor":"조지아"}},"KR":{"currency":["KRW"],"callingCode":["82"],"region":"Asia","subregion":"Eastern Asia","flag":"flag-kr","name":{"common":"South Korea","ces":"Jižní Korea","deu":"Südkorea","fra":"Corée du Sud","hrv":"Južna Koreja","ita":"Corea del Sud","jpn":"韓国","nld":"Zuid-Korea","por":"Coreia do Sul","rus":"Южная Корея","slk":"Južná Kórea","spa":"Corea del Sur","fin":"Etelä-Korea","est":"Lõuna-Korea","zho":"韩国","pol":"Korea Południowa","urd":"جنوبی کوریا","kor":"한국"}},"SS":{"currency":["SSP"],"callingCode":["211"],"region":"Africa","subregion":"Middle Africa","flag":"flag-ss","name":{"common":"South Sudan","ces":"Jižní Súdán","deu":"Südsudan","fra":"Soudan du Sud","hrv":"Južni Sudan","ita":"Sudan del sud","jpn":"南スーダン","nld":"Zuid-Soedan","por":"Sudão do Sul","rus":"Южный Судан","slk":"Južný Sudán","spa":"Sudán del Sur","fin":"Etelä-Sudan","est":"Lõuna-Sudaan","zho":"南苏丹","pol":"Sudan","urd":"جنوبی سوڈان","kor":"남수단"}},"ES":{"currency":["EUR"],"callingCode":["34"],"region":"Europe","subregion":"Southern Europe","flag":"flag-es","name":{"common":"Spain","ces":"Španělsko","deu":"Spanien","fra":"Espagne","hrv":"Španjolska","ita":"Spagna","jpn":"スペイン","nld":"Spanje","por":"Espanha","rus":"Испания","slk":"Španielsko","spa":"España","fin":"Espanja","est":"Hispaania","zho":"西班牙","pol":"Hiszpania","urd":"ہسپانیہ","kor":"스페인"}},"LK":{"currency":["LKR"],"callingCode":["94"],"region":"Asia","subregion":"Southern Asia","flag":"flag-lk","name":{"common":"Sri Lanka","ces":"Srí Lanka","deu":"Sri Lanka","fra":"Sri Lanka","hrv":"Šri Lanka","ita":"Sri Lanka","jpn":"スリランカ","nld":"Sri Lanka","por":"Sri Lanka","rus":"Шри-Ланка","slk":"Srí Lanka","spa":"Sri Lanka","fin":"Sri Lanka","est":"Sri Lanka","zho":"斯里兰卡","pol":"Sri Lanka","urd":"سری لنکا","kor":"스리랑카"}},"SD":{"currency":["SDG"],"callingCode":["249"],"region":"Africa","subregion":"Northern Africa","flag":"flag-sd","name":{"common":"Sudan","ces":"Súdán","deu":"Sudan","fra":"Soudan","hrv":"Sudan","ita":"Sudan","jpn":"スーダン","nld":"Soedan","por":"Sudão","rus":"Судан","slk":"Sudán","spa":"Sudán","fin":"Sudan","est":"Sudaan","zho":"苏丹","pol":"Sudan","urd":"سودان","kor":"수단"}},"SR":{"currency":["SRD"],"callingCode":["597"],"region":"Americas","subregion":"South America","flag":"flag-sr","name":{"common":"Suriname","ces":"Surinam","deu":"Suriname","fra":"Surinam","hrv":"Surinam","ita":"Suriname","jpn":"スリナム","nld":"Suriname","por":"Suriname","rus":"Суринам","slk":"Surinam","spa":"Surinam","fin":"Suriname","est":"Suriname","zho":"苏里南","pol":"Surinam","urd":"سرینام","kor":"수리남"}},"SJ":{"currency":["NOK"],"callingCode":["4779"],"region":"Europe","subregion":"Northern Europe","flag":"flag-sj","name":{"common":"Svalbard and Jan Mayen","ces":"Špicberky a Jan Mayen","deu":"Spitzbergen und Jan Mayen","fra":"Svalbard et Jan Mayen","hrv":"Svalbard i Jan Mayen","ita":"Svalbard e Jan Mayen","jpn":"スヴァールバル諸島およびヤンマイエン島","nld":"Svalbard en Jan Mayen","por":"Ilhas Svalbard e Jan Mayen","rus":"Шпицберген и Ян-Майен","slk":"Svalbard a Jan Mayen","spa":"Islas Svalbard y Jan Mayen","fin":"Huippuvuoret","est":"Svalbard","zho":"斯瓦尔巴特","pol":"Svalbard i Jan Mayen","urd":"سوالبارڈ اور جان میئن","kor":"스발바르 얀마옌 제도"}},"SE":{"currency":["SEK"],"callingCode":["46"],"region":"Europe","subregion":"Northern Europe","flag":"flag-se","name":{"common":"Sweden","ces":"Švédsko","deu":"Schweden","fra":"Suède","hrv":"Švedska","ita":"Svezia","jpn":"スウェーデン","nld":"Zweden","por":"Suécia","rus":"Швеция","slk":"Švédsko","spa":"Suecia","fin":"Ruotsi","est":"Rootsi","zho":"瑞典","pol":"Szwecja","urd":"سویڈن","kor":"스웨덴"}},"CH":{"currency":["CHF"],"callingCode":["41"],"region":"Europe","subregion":"Western Europe","flag":"flag-ch","name":{"common":"Switzerland","ces":"Švýcarsko","deu":"Schweiz","fra":"Suisse","hrv":"Švicarska","ita":"Svizzera","jpn":"スイス","nld":"Zwitserland","por":"Suíça","rus":"Швейцария","slk":"Švajčiarsko","spa":"Suiza","fin":"Sveitsi","est":"Šveits","zho":"瑞士","pol":"Szwajcaria","urd":"سویٹذرلینڈ","kor":"스위스"}},"SY":{"currency":["SYP"],"callingCode":["963"],"region":"Asia","subregion":"Western Asia","flag":"flag-sy","name":{"common":"Syria","ces":"Sýrie","deu":"Syrien","fra":"Syrie","hrv":"Sirija","ita":"Siria","jpn":"シリア・アラブ共和国","nld":"Syrië","por":"Síria","rus":"Сирия","slk":"Sýria","spa":"Siria","fin":"Syyria","est":"Süüria","zho":"叙利亚","pol":"Syria","urd":"سوریہ","kor":"시리아"}},"ST":{"currency":["STD"],"callingCode":["239"],"region":"Africa","subregion":"Middle Africa","flag":"flag-st","name":{"common":"São Tomé and Príncipe","ces":"Svatý Tomáš a Princův ostrov","deu":"São Tomé und Príncipe","fra":"São Tomé et Príncipe","hrv":"Sveti Toma i Princip","ita":"São Tomé e Príncipe","jpn":"サントメ・プリンシペ","nld":"Sao Tomé en Principe","por":"São Tomé e Príncipe","spa":"Santo Tomé y Príncipe","rus":"Сан-Томе и Принсипи","slk":"Svätý Tomáš a Princov ostrov","fin":"São Téme ja Príncipe","est":"São Tomé ja Príncipe","zho":"圣多美和普林西比","pol":"Wyspy Świętego Tomasza i Książęca","urd":"ساؤ ٹومے و پرنسپے","kor":"상투메 프린시페"}},"TW":{"currency":["TWD"],"callingCode":["886"],"region":"Asia","subregion":"Eastern Asia","flag":"flag-tw","name":{"common":"Taiwan","ces":"Tchaj-wan","deu":"Taiwan","fra":"Taïwan","hrv":"Tajvan","ita":"Taiwan","jpn":"台湾","nld":"Taiwan","por":"Ilha Formosa","rus":"Тайвань","slk":"Taiwan","spa":"Taiwán","fin":"Taiwan","est":"Taiwan","pol":"Tajwan","urd":"تائیوان","kor":"대만"}},"TJ":{"currency":["TJS"],"callingCode":["992"],"region":"Asia","subregion":"Central Asia","flag":"flag-tj","name":{"common":"Tajikistan","ces":"Tádžikistán","deu":"Tadschikistan","fra":"Tadjikistan","hrv":"Tađikistan","ita":"Tagikistan","jpn":"タジキスタン","nld":"Tadzjikistan","por":"Tajiquistão","rus":"Таджикистан","slk":"Tadžikistan","spa":"Tayikistán","fin":"Tadžikistan","est":"Tadžikistan","zho":"塔吉克斯坦","pol":"Tadżykistan","urd":"تاجکستان","kor":"타지키스탄"}},"TZ":{"currency":["TZS"],"callingCode":["255"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-tz","name":{"common":"Tanzania","ces":"Tanzanie","deu":"Tansania","fra":"Tanzanie","hrv":"Tanzanija","ita":"Tanzania","jpn":"タンザニア","nld":"Tanzania","por":"Tanzânia","rus":"Танзания","slk":"Tanzánia","spa":"Tanzania","fin":"Tansania","est":"Tansaania","zho":"坦桑尼亚","pol":"Tanzania","urd":"تنزانیہ","kor":"탄자니아"}},"TH":{"currency":["THB"],"callingCode":["66"],"region":"Asia","subregion":"South-Eastern Asia","flag":"flag-th","name":{"common":"Thailand","ces":"Thajsko","deu":"Thailand","fra":"Thaïlande","hrv":"Tajland","ita":"Tailandia","jpn":"タイ","nld":"Thailand","por":"Tailândia","rus":"Таиланд","slk":"Thajsko","spa":"Tailandia","fin":"Thaimaa","est":"Tai","zho":"泰国","pol":"Tajlandia","urd":"تھائی لینڈ","kor":"태국"}},"TL":{"currency":["USD"],"callingCode":["670"],"region":"Asia","subregion":"South-Eastern Asia","flag":"flag-tl","name":{"common":"Timor-Leste","ces":"Východní Timor","deu":"Osttimor","fra":"Timor oriental","hrv":"Istočni Timor","ita":"Timor Est","jpn":"東ティモール","nld":"Oost-Timor","por":"Timor-Leste","rus":"Восточный Тимор","slk":"Východný Timor","spa":"Timor Oriental","fin":"Itä-Timor","est":"Ida-Timor","zho":"东帝汶","pol":"Timor Wschodni","urd":"مشرقی تیمور","kor":"동티모르"}},"TG":{"currency":["XOF"],"callingCode":["228"],"region":"Africa","subregion":"Western Africa","flag":"flag-tg","name":{"common":"Togo","ces":"Togo","deu":"Togo","fra":"Togo","hrv":"Togo","ita":"Togo","jpn":"トーゴ","nld":"Togo","por":"Togo","rus":"Того","slk":"Togo","spa":"Togo","fin":"Togo","est":"Togo","zho":"多哥","pol":"Togo","urd":"ٹوگو","kor":"토고"}},"TK":{"currency":["NZD"],"callingCode":["690"],"region":"Oceania","subregion":"Polynesia","flag":"flag-tk","name":{"common":"Tokelau","ces":"Tokelau","deu":"Tokelau","fra":"Tokelau","hrv":"Tokelau","ita":"Isole Tokelau","jpn":"トケラウ","nld":"Tokelau","por":"Tokelau","rus":"Токелау","slk":"Tokelau","spa":"Islas Tokelau","fin":"Tokelau","est":"Tokelau","zho":"托克劳","pol":"Tokelau","urd":"ٹوکیلاؤ","kor":"토켈라우"}},"TO":{"currency":["TOP"],"callingCode":["676"],"region":"Oceania","subregion":"Polynesia","flag":"flag-to","name":{"common":"Tonga","ces":"Tonga","deu":"Tonga","fra":"Tonga","hrv":"Tonga","ita":"Tonga","jpn":"トンガ","nld":"Tonga","por":"Tonga","rus":"Тонга","slk":"Tonga","spa":"Tonga","fin":"Tonga","est":"Tonga","zho":"汤加","pol":"Tonga","urd":"ٹونگا","kor":"통가"}},"TT":{"currency":["TTD"],"callingCode":["1868"],"region":"Americas","subregion":"Caribbean","flag":"flag-tt","name":{"common":"Trinidad and Tobago","ces":"Trinidad a Tobago","deu":"Trinidad und Tobago","fra":"Trinité-et-Tobago","hrv":"Trinidad i Tobago","ita":"Trinidad e Tobago","jpn":"トリニダード・トバゴ","nld":"Trinidad en Tobago","por":"Trinidade e Tobago","rus":"Тринидад и Тобаго","slk":"Trinidad a Tobago","spa":"Trinidad y Tobago","fin":"Trinidad ja Tobago","est":"Trinidad ja Tobago","zho":"特立尼达和多巴哥","pol":"Trynidad i Tobago","urd":"ٹرینیڈاڈ و ٹوباگو","kor":"트리니다드 토바고"}},"TN":{"currency":["TND"],"callingCode":["216"],"region":"Africa","subregion":"Northern Africa","flag":"flag-tn","name":{"common":"Tunisia","ces":"Tunisko","deu":"Tunesien","fra":"Tunisie","hrv":"Tunis","ita":"Tunisia","jpn":"チュニジア","nld":"Tunesië","por":"Tunísia","rus":"Тунис","slk":"Tunisko","spa":"Túnez","fin":"Tunisia","est":"Tuneesia","zho":"突尼斯","pol":"Tunezja","urd":"تونس","kor":"튀니지"}},"TR":{"currency":["TRY"],"callingCode":["90"],"region":"Asia","subregion":"Western Asia","flag":"flag-tr","name":{"common":"Turkey","ces":"Turecko","deu":"Türkei","fra":"Turquie","hrv":"Turska","ita":"Turchia","jpn":"トルコ","nld":"Turkije","por":"Turquia","rus":"Турция","slk":"Turecko","spa":"Turquía","fin":"Turkki","est":"Türgi","zho":"土耳其","pol":"Turcja","urd":"ترکی","kor":"터키"}},"TM":{"currency":["TMT"],"callingCode":["993"],"region":"Asia","subregion":"Central Asia","flag":"flag-tm","name":{"common":"Turkmenistan","ces":"Turkmenistán","deu":"Turkmenistan","fra":"Turkménistan","hrv":"Turkmenistan","ita":"Turkmenistan","jpn":"トルクメニスタン","nld":"Turkmenistan","por":"Turquemenistão","rus":"Туркмения","slk":"Turkménsko","spa":"Turkmenistán","fin":"Turkmenistan","est":"Türkmenistan","zho":"土库曼斯坦","pol":"Turkmenistan","urd":"ترکمانستان","kor":"투르크메니스탄"}},"TC":{"currency":["USD"],"callingCode":["1649"],"region":"Americas","subregion":"Caribbean","flag":"flag-tc","name":{"common":"Turks and Caicos Islands","ces":"Turks a Caicos","deu":"Turks-und Caicosinseln","fra":"Îles Turques-et-Caïques","hrv":"Otoci Turks i Caicos","ita":"Isole Turks e Caicos","jpn":"タークス・カイコス諸島","nld":"Turks-en Caicoseilanden","por":"Ilhas Turks e Caicos","rus":"Теркс и Кайкос","slk":"Turks a Caicos","spa":"Islas Turks y Caicos","fin":"Turks-ja Caicossaaret","est":"Turks ja Caicos","zho":"特克斯和凯科斯群岛","pol":"Turks i Caicos","urd":"جزائر کیکس و ترکیہ","kor":"터크스 케이커스 제도"}},"TV":{"currency":["AUD"],"callingCode":["688"],"region":"Oceania","subregion":"Polynesia","flag":"flag-tv","name":{"common":"Tuvalu","ces":"Tuvalu","deu":"Tuvalu","fra":"Tuvalu","hrv":"Tuvalu","ita":"Tuvalu","jpn":"ツバル","nld":"Tuvalu","por":"Tuvalu","rus":"Тувалу","slk":"Tuvalu","spa":"Tuvalu","fin":"Tuvalu","est":"Tuvalu","zho":"图瓦卢","pol":"Tuvalu","urd":"تووالو","kor":"투발루"}},"UG":{"currency":["UGX"],"callingCode":["256"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-ug","name":{"common":"Uganda","ces":"Uganda","deu":"Uganda","fra":"Ouganda","hrv":"Uganda","ita":"Uganda","jpn":"ウガンダ","nld":"Oeganda","por":"Uganda","rus":"Уганда","slk":"Uganda","spa":"Uganda","fin":"Uganda","est":"Uganda","zho":"乌干达","pol":"Uganda","urd":"یوگنڈا","kor":"우간다"}},"UA":{"currency":["UAH"],"callingCode":["380"],"region":"Europe","subregion":"Eastern Europe","flag":"flag-ua","name":{"common":"Ukraine","ces":"Ukrajina","deu":"Ukraine","fra":"Ukraine","hrv":"Ukrajina","ita":"Ucraina","jpn":"ウクライナ","nld":"Oekraïne","por":"Ucrânia","rus":"Украина","slk":"Ukrajina","spa":"Ucrania","fin":"Ukraina","est":"Ukraina","zho":"乌克兰","pol":"Ukraina","urd":"یوکرین","kor":"우크라이나"}},"AE":{"currency":["AED"],"callingCode":["971"],"region":"Asia","subregion":"Western Asia","flag":"flag-ae","name":{"common":"United Arab Emirates","ces":"Spojené arabské emiráty","deu":"Vereinigte Arabische Emirate","fra":"Émirats arabes unis","hrv":"Ujedinjeni Arapski Emirati","ita":"Emirati Arabi Uniti","jpn":"アラブ首長国連邦","nld":"Verenigde Arabische Emiraten","por":"Emirados Árabes Unidos","rus":"Объединённые Арабские Эмираты","slk":"Spojené arabské emiráty","spa":"Emiratos Árabes Unidos","fin":"Arabiemiraatit","est":"Araabia Ühendemiraadid","zho":"阿拉伯联合酋长国","pol":"Zjednoczone Emiraty Arabskie","urd":"متحدہ عرب امارات","kor":"아랍에미리트"}},"GB":{"currency":["GBP"],"callingCode":["44"],"region":"Europe","subregion":"Northern Europe","flag":"flag-gb","name":{"common":"United Kingdom","ces":"Spojené království","deu":"Vereinigtes Königreich","fra":"Royaume-Uni","hrv":"Ujedinjeno Kraljevstvo","ita":"Regno Unito","jpn":"イギリス","nld":"Verenigd Koninkrijk","por":"Reino Unido","rus":"Великобритания","slk":"Veľká Británia (Spojené kráľovstvo)","spa":"Reino Unido","fin":"Yhdistynyt kuningaskunta","est":"Suurbritannia","zho":"英国","pol":"Zjednoczone Krłlestwo","urd":"مملکتِ متحدہ","kor":"영국"}},"US":{"currency":["USD"],"callingCode":["1"],"region":"Americas","subregion":"North America","flag":"flag-us","name":{"common":"United States","ces":"Spojené státy","deu":"Vereinigte Staaten","fra":"États-Unis","hrv":"Sjedinjene Američke Države","ita":"Stati Uniti d'America","jpn":"アメリカ合衆国","nld":"Verenigde Staten","por":"Estados Unidos","rus":"Соединённые Штаты Америки","slk":"Spojené štáty americké","spa":"Estados Unidos","fin":"Yhdysvallat","est":"Ameerika Ühendriigid","zho":"美国","pol":"Stany Zjednoczone","urd":"ریاستہائے متحدہ","kor":"미국"}},"UM":{"currency":["USD"],"callingCode":[],"region":"Americas","subregion":"North America","flag":"flag-um","name":{"common":"United States Minor Outlying Islands","ces":"Menší odlehlé ostrovy USA","deu":"Kleinere Inselbesitzungen der Vereinigten Staaten","fra":"Îles mineures éloignées des États-Unis","hrv":"Mali udaljeni otoci SAD-a","ita":"Isole minori esterne degli Stati Uniti d'America","jpn":"合衆国領有小離島","nld":"Kleine afgelegen eilanden van de Verenigde Staten","por":"Ilhas Menores Distantes dos Estados Unidos","rus":"Внешние малые острова США","slk":"Menšie odľahlé ostrovy USA","spa":"Islas Ultramarinas Menores de Estados Unidos","fin":"Yhdysvaltain asumattomat saaret","est":"Ühendriikide hajasaared","zho":"美国本土外小岛屿","pol":"Dalekie Wyspy Mniejsze Stanów Zjednoczonych","urd":"امریکی چھوٹے بیرونی جزائر","kor":"미국령 군소 제도"}},"VI":{"currency":["USD"],"callingCode":["1340"],"region":"Americas","subregion":"Caribbean","flag":"flag-vi","name":{"common":"United States Virgin Islands","ces":"Americké Panenské ostrovy","deu":"Amerikanische Jungferninseln","fra":"Îles Vierges des États-Unis","hrv":"Američki Djevičanski Otoci","ita":"Isole Vergini americane","jpn":"アメリカ領ヴァージン諸島","nld":"Amerikaanse Maagdeneilanden","por":"Ilhas Virgens dos Estados Unidos","rus":"Виргинские Острова","slk":"Americké Panenské ostrovy","spa":"Islas Vírgenes de los Estados Unidos","fin":"Neitsytsaaret","est":"Neitsisaared, USA","zho":"美属维尔京群岛","pol":"Wyspy Dziewicze Stanów Zjednoczonych","urd":"امریکی جزائر ورجن","kor":"미국령 버진아일랜드"}},"UY":{"currency":["UYU"],"callingCode":["598"],"region":"Americas","subregion":"South America","flag":"flag-uy","name":{"common":"Uruguay","ces":"Uruguay","deu":"Uruguay","fra":"Uruguay","hrv":"Urugvaj","ita":"Uruguay","jpn":"ウルグアイ","nld":"Uruguay","por":"Uruguai","rus":"Уругвай","slk":"Uruguaj","spa":"Uruguay","fin":"Uruguay","est":"Uruguay","zho":"乌拉圭","pol":"Urugwaj","urd":"یوراگوئے","kor":"우루과이"}},"UZ":{"currency":["UZS"],"callingCode":["998"],"region":"Asia","subregion":"Central Asia","flag":"flag-uz","name":{"common":"Uzbekistan","ces":"Uzbekistán","deu":"Usbekistan","fra":"Ouzbékistan","hrv":"Uzbekistan","ita":"Uzbekistan","jpn":"ウズベキスタン","nld":"Oezbekistan","por":"Uzbequistão","rus":"Узбекистан","slk":"Uzbekistan","spa":"Uzbekistán","fin":"Uzbekistan","est":"Usbekistan","zho":"乌兹别克斯坦","pol":"Uzbekistan","urd":"ازبکستان","kor":"우즈베키스탄"}},"VU":{"currency":["VUV"],"callingCode":["678"],"region":"Oceania","subregion":"Melanesia","flag":"flag-vu","name":{"common":"Vanuatu","ces":"Vanuatu","deu":"Vanuatu","fra":"Vanuatu","hrv":"Vanuatu","ita":"Vanuatu","jpn":"バヌアツ","nld":"Vanuatu","por":"Vanuatu","rus":"Вануату","slk":"Vanuatu","spa":"Vanuatu","fin":"Vanuatu","est":"Vanuatu","zho":"瓦努阿图","pol":"Vanuatu","urd":"وانواتو","kor":"바누아투"}},"VA":{"currency":["EUR"],"callingCode":["3906698","379"],"region":"Europe","subregion":"Southern Europe","flag":"flag-va","name":{"common":"Vatican City","ces":"Vatikán","deu":"Vatikanstadt","fra":"Cité du Vatican","hrv":"Vatikan","ita":"Città del Vaticano","jpn":"バチカン市国","nld":"Vaticaanstad","por":"Cidade do Vaticano","rus":"Ватикан","slk":"Vatikán","spa":"Ciudad del Vaticano","fin":"Vatikaani","est":"Vatikan","zho":"梵蒂冈","pol":"Watykan","urd":"ویٹیکن سٹی","kor":"바티칸"}},"VE":{"currency":["VEF"],"callingCode":["58"],"region":"Americas","subregion":"South America","flag":"flag-ve","name":{"common":"Venezuela","ces":"Venezuela","deu":"Venezuela","fra":"Venezuela","hrv":"Venezuela","ita":"Venezuela","jpn":"ベネズエラ・ボリバル共和国","nld":"Venezuela","por":"Venezuela","rus":"Венесуэла","slk":"Venezuela","spa":"Venezuela","fin":"Venezuela","est":"Venezuela","zho":"委内瑞拉","pol":"Wenezuela","urd":"وینیزویلا","kor":"베네수엘라"}},"VN":{"currency":["VND"],"callingCode":["84"],"region":"Asia","subregion":"South-Eastern Asia","flag":"flag-vn","name":{"common":"Vietnam","ces":"Vietnam","deu":"Vietnam","fra":"Viêt Nam","hrv":"Vijetnam","ita":"Vietnam","jpn":"ベトナム","nld":"Vietnam","por":"Vietname","rus":"Вьетнам","slk":"Vietnam","spa":"Vietnam","fin":"Vietnam","est":"Vietnam","zho":"越南","pol":"Wietnam","urd":"ویتنام","kor":"베트남"}},"WF":{"currency":["XPF"],"callingCode":["681"],"region":"Oceania","subregion":"Polynesia","flag":"flag-wf","name":{"common":"Wallis and Futuna","ces":"Wallis a Futuna","deu":"Wallis und Futuna","fra":"Wallis-et-Futuna","hrv":"Wallis i Fortuna","ita":"Wallis e Futuna","jpn":"ウォリス・フツナ","nld":"Wallis en Futuna","por":"Wallis e Futuna","rus":"Уоллис и Футуна","slk":"Wallis a Futuna","spa":"Wallis y Futuna","fin":"Wallis ja Futuna","est":"Wallis ja Futuna","zho":"瓦利斯和富图纳群岛","pol":"Wallis i Futuna","urd":"والس و فتونہ","kor":""}},"EH":{"currency":["MAD","DZD","MRO"],"callingCode":["212"],"region":"Africa","subregion":"Northern Africa","flag":"flag-eh","name":{"common":"Western Sahara","ces":"Západní Sahara","deu":"Westsahara","fra":"Sahara Occidental","hrv":"Zapadna Sahara","ita":"Sahara Occidentale","jpn":"西サハラ","nld":"Westelijke Sahara","por":"Saara Ocidental","rus":"Западная Сахара","slk":"Západná Sahara","spa":"Sahara Occidental","fin":"Länsi-Sahara","est":"Lääne-Sahara","zho":"西撒哈拉","pol":"Sahara Zachodnia","urd":"مغربی صحارا","kor":"서사하라"}},"YE":{"currency":["YER"],"callingCode":["967"],"region":"Asia","subregion":"Western Asia","flag":"flag-ye","name":{"common":"Yemen","ces":"Jemen","deu":"Jemen","fra":"Yémen","hrv":"Jemen","ita":"Yemen","jpn":"イエメン","nld":"Jemen","por":"Iémen","rus":"Йемен","slk":"Jemen","spa":"Yemen","fin":"Jemen","est":"Jeemen","zho":"也门","pol":"Jemen","urd":"یمن","kor":"예멘"}},"ZM":{"currency":["ZMW"],"callingCode":["260"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-zm","name":{"common":"Zambia","ces":"Zambie","deu":"Sambia","fra":"Zambie","hrv":"Zambija","ita":"Zambia","jpn":"ザンビア","nld":"Zambia","por":"Zâmbia","rus":"Замбия","slk":"Zambia","spa":"Zambia","fin":"Sambia","est":"Sambia","zho":"赞比亚","pol":"Zambia","urd":"زیمبیا","kor":"잠비아"}},"ZW":{"currency":["ZWL"],"callingCode":["263"],"region":"Africa","subregion":"Eastern Africa","flag":"flag-zw","name":{"common":"Zimbabwe","ces":"Zimbabwe","deu":"Simbabwe","fra":"Zimbabwe","hrv":"Zimbabve","ita":"Zimbabwe","jpn":"ジンバブエ","nld":"Zimbabwe","por":"Zimbabwe","rus":"Зимбабве","slk":"Zimbabwe","spa":"Zimbabue","fin":"Zimbabwe","est":"Zimbabwe","zho":"津巴布韦","pol":"Zimbabwe","urd":"زمبابوے","kor":"짐바브웨"}},"AX":{"currency":["EUR"],"callingCode":["358"],"region":"Europe","subregion":"Northern Europe","flag":"flag-ax","name":{"common":"Åland Islands","ces":"Ålandy","deu":"Åland","fra":"Ahvenanmaa","hrv":"Ålandski otoci","ita":"Isole Aland","jpn":"オーランド諸島","nld":"Ålandeilanden","por":"Alândia","rus":"Аландские острова","slk":"Alandy","spa":"Alandia","fin":"Ahvenanmaa","est":"Ahvenamaa","zho":"奥兰群岛","pol":"Wyspy Alandzkie","urd":"جزائر اولند","kor":"올란드 제도"}}} \ No newline at end of file diff --git a/app/src/main/res/raw/node_modules_reactnativephoneinput_lib_resources_countries.json b/app/src/main/res/raw/node_modules_reactnativephoneinput_lib_resources_countries.json deleted file mode 100644 index 0cb35ead..00000000 --- a/app/src/main/res/raw/node_modules_reactnativephoneinput_lib_resources_countries.json +++ /dev/null @@ -1,1746 +0,0 @@ -[ - { - "name": "Afghanistan (‫افغانستان‬‎)", - "iso2": "af", - "dialCode": "93", - "priority": 0, - "areaCodes": null - }, - { - "name": "Albania (Shqipëri)", - "iso2": "al", - "dialCode": "355", - "priority": 0, - "areaCodes": null - }, - { - "name": "Algeria (‫الجزائر‬‎)", - "iso2": "dz", - "dialCode": "213", - "priority": 0, - "areaCodes": null - }, - { - "name": "American Samoa", - "iso2": "as", - "dialCode": "1684", - "priority": 0, - "areaCodes": null - }, - { - "name": "Andorra", - "iso2": "ad", - "dialCode": "376", - "priority": 0, - "areaCodes": null - }, - { - "name": "Angola", - "iso2": "ao", - "dialCode": "244", - "priority": 0, - "areaCodes": null - }, - { - "name": "Anguilla", - "iso2": "ai", - "dialCode": "1264", - "priority": 0, - "areaCodes": null - }, - { - "name": "Antigua and Barbuda", - "iso2": "ag", - "dialCode": "1268", - "priority": 0, - "areaCodes": null - }, - { - "name": "Argentina", - "iso2": "ar", - "dialCode": "54", - "priority": 0, - "areaCodes": null - }, - { - "name": "Armenia (Հայաստան)", - "iso2": "am", - "dialCode": "374", - "priority": 0, - "areaCodes": null - }, - { - "name": "Aruba", - "iso2": "aw", - "dialCode": "297", - "priority": 0, - "areaCodes": null - }, - { - "name": "Australia", - "iso2": "au", - "dialCode": "61", - "priority": 0, - "areaCodes": null - }, - { - "name": "Austria (Österreich)", - "iso2": "at", - "dialCode": "43", - "priority": 0, - "areaCodes": null - }, - { - "name": "Azerbaijan (Azərbaycan)", - "iso2": "az", - "dialCode": "994", - "priority": 0, - "areaCodes": null - }, - { - "name": "Bahamas", - "iso2": "bs", - "dialCode": "1242", - "priority": 0, - "areaCodes": null - }, - { - "name": "Bahrain (‫البحرين‬‎)", - "iso2": "bh", - "dialCode": "973", - "priority": 0, - "areaCodes": null - }, - { - "name": "Bangladesh (বাংলাদেশ)", - "iso2": "bd", - "dialCode": "880", - "priority": 0, - "areaCodes": null - }, - { - "name": "Barbados", - "iso2": "bb", - "dialCode": "1246", - "priority": 0, - "areaCodes": null - }, - { - "name": "Belarus (Беларусь)", - "iso2": "by", - "dialCode": "375", - "priority": 0, - "areaCodes": null - }, - { - "name": "Belgium (België)", - "iso2": "be", - "dialCode": "32", - "priority": 0, - "areaCodes": null - }, - { - "name": "Belize", - "iso2": "bz", - "dialCode": "501", - "priority": 0, - "areaCodes": null - }, - { - "name": "Benin (Bénin)", - "iso2": "bj", - "dialCode": "229", - "priority": 0, - "areaCodes": null - }, - { - "name": "Bermuda", - "iso2": "bm", - "dialCode": "1441", - "priority": 0, - "areaCodes": null - }, - { - "name": "Bhutan (འབྲུག)", - "iso2": "bt", - "dialCode": "975", - "priority": 0, - "areaCodes": null - }, - { - "name": "Bolivia", - "iso2": "bo", - "dialCode": "591", - "priority": 0, - "areaCodes": null - }, - { - "name": "Bosnia and Herzegovina (Босна и Херцеговина)", - "iso2": "ba", - "dialCode": "387", - "priority": 0, - "areaCodes": null - }, - { - "name": "Botswana", - "iso2": "bw", - "dialCode": "267", - "priority": 0, - "areaCodes": null - }, - { - "name": "Brazil (Brasil)", - "iso2": "br", - "dialCode": "55", - "priority": 0, - "areaCodes": null - }, - { - "name": "British Indian Ocean Territory", - "iso2": "io", - "dialCode": "246", - "priority": 0, - "areaCodes": null - }, - { - "name": "British Virgin Islands", - "iso2": "vg", - "dialCode": "1284", - "priority": 0, - "areaCodes": null - }, - { - "name": "Brunei", - "iso2": "bn", - "dialCode": "673", - "priority": 0, - "areaCodes": null - }, - { - "name": "Bulgaria (България)", - "iso2": "bg", - "dialCode": "359", - "priority": 0, - "areaCodes": null - }, - { - "name": "Burkina Faso", - "iso2": "bf", - "dialCode": "226", - "priority": 0, - "areaCodes": null - }, - { - "name": "Burundi (Uburundi)", - "iso2": "bi", - "dialCode": "257", - "priority": 0, - "areaCodes": null - }, - { - "name": "Cambodia (កម្ពុជា)", - "iso2": "kh", - "dialCode": "855", - "priority": 0, - "areaCodes": null - }, - { - "name": "Cameroon (Cameroun)", - "iso2": "cm", - "dialCode": "237", - "priority": 0, - "areaCodes": null - }, - { - "name": "Canada", - "iso2": "ca", - "dialCode": "1", - "priority": 1, - "areaCodes": [ - "204", - "226", - "236", - "249", - "250", - "289", - "306", - "343", - "365", - "387", - "403", - "416", - "418", - "431", - "437", - "438", - "450", - "506", - "514", - "519", - "548", - "579", - "581", - "587", - "604", - "613", - "639", - "647", - "672", - "705", - "709", - "742", - "778", - "780", - "782", - "807", - "819", - "825", - "867", - "873", - "902", - "905" - ] - }, - { - "name": "Cape Verde (Kabu Verdi)", - "iso2": "cv", - "dialCode": "238", - "priority": 0, - "areaCodes": null - }, - { - "name": "Caribbean Netherlands", - "iso2": "bq", - "dialCode": "599", - "priority": 1, - "areaCodes": null - }, - { - "name": "Cayman Islands", - "iso2": "ky", - "dialCode": "1345", - "priority": 0, - "areaCodes": null - }, - { - "name": "Central African Republic (République centrafricaine)", - "iso2": "cf", - "dialCode": "236", - "priority": 0, - "areaCodes": null - }, - { - "name": "Chad (Tchad)", - "iso2": "td", - "dialCode": "235", - "priority": 0, - "areaCodes": null - }, - { - "name": "Chile", - "iso2": "cl", - "dialCode": "56", - "priority": 0, - "areaCodes": null - }, - { - "name": "China (中国)", - "iso2": "cn", - "dialCode": "86", - "priority": 0, - "areaCodes": null - }, - { - "name": "Christmas Island", - "iso2": "cx", - "dialCode": "61", - "priority": 2, - "areaCodes": null - }, - { - "name": "Cocos (Keeling) Islands", - "iso2": "cc", - "dialCode": "61", - "priority": 1, - "areaCodes": null - }, - { - "name": "Colombia", - "iso2": "co", - "dialCode": "57", - "priority": 0, - "areaCodes": null - }, - { - "name": "Comoros (‫جزر القمر‬‎)", - "iso2": "km", - "dialCode": "269", - "priority": 0, - "areaCodes": null - }, - { - "name": "Congo (DRC) (Jamhuri ya Kidemokrasia ya Kongo)", - "iso2": "cd", - "dialCode": "243", - "priority": 0, - "areaCodes": null - }, - { - "name": "Congo (Republic) (Congo-Brazzaville)", - "iso2": "cg", - "dialCode": "242", - "priority": 0, - "areaCodes": null - }, - { - "name": "Cook Islands", - "iso2": "ck", - "dialCode": "682", - "priority": 0, - "areaCodes": null - }, - { - "name": "Costa Rica", - "iso2": "cr", - "dialCode": "506", - "priority": 0, - "areaCodes": null - }, - { - "name": "Côte d’Ivoire", - "iso2": "ci", - "dialCode": "225", - "priority": 0, - "areaCodes": null - }, - { - "name": "Croatia (Hrvatska)", - "iso2": "hr", - "dialCode": "385", - "priority": 0, - "areaCodes": null - }, - { - "name": "Cuba", - "iso2": "cu", - "dialCode": "53", - "priority": 0, - "areaCodes": null - }, - { - "name": "Curaçao", - "iso2": "cw", - "dialCode": "599", - "priority": 0, - "areaCodes": null - }, - { - "name": "Cyprus (Κύπρος)", - "iso2": "cy", - "dialCode": "357", - "priority": 0, - "areaCodes": null - }, - { - "name": "Czech Republic (Česká republika)", - "iso2": "cz", - "dialCode": "420", - "priority": 0, - "areaCodes": null - }, - { - "name": "Denmark (Danmark)", - "iso2": "dk", - "dialCode": "45", - "priority": 0, - "areaCodes": null - }, - { - "name": "Djibouti", - "iso2": "dj", - "dialCode": "253", - "priority": 0, - "areaCodes": null - }, - { - "name": "Dominica", - "iso2": "dm", - "dialCode": "1767", - "priority": 0, - "areaCodes": null - }, - { - "name": "Dominican Republic (República Dominicana)", - "iso2": "do", - "dialCode": "1", - "priority": 2, - "areaCodes": [ - "809", - "829", - "849" - ] - }, - { - "name": "Ecuador", - "iso2": "ec", - "dialCode": "593", - "priority": 0, - "areaCodes": null - }, - { - "name": "Egypt (‫مصر‬‎)", - "iso2": "eg", - "dialCode": "20", - "priority": 0, - "areaCodes": null - }, - { - "name": "El Salvador", - "iso2": "sv", - "dialCode": "503", - "priority": 0, - "areaCodes": null - }, - { - "name": "Equatorial Guinea (Guinea Ecuatorial)", - "iso2": "gq", - "dialCode": "240", - "priority": 0, - "areaCodes": null - }, - { - "name": "Eritrea", - "iso2": "er", - "dialCode": "291", - "priority": 0, - "areaCodes": null - }, - { - "name": "Estonia (Eesti)", - "iso2": "ee", - "dialCode": "372", - "priority": 0, - "areaCodes": null - }, - { - "name": "Ethiopia", - "iso2": "et", - "dialCode": "251", - "priority": 0, - "areaCodes": null - }, - { - "name": "Falkland Islands (Islas Malvinas)", - "iso2": "fk", - "dialCode": "500", - "priority": 0, - "areaCodes": null - }, - { - "name": "Faroe Islands (Føroyar)", - "iso2": "fo", - "dialCode": "298", - "priority": 0, - "areaCodes": null - }, - { - "name": "Fiji", - "iso2": "fj", - "dialCode": "679", - "priority": 0, - "areaCodes": null - }, - { - "name": "Finland (Suomi)", - "iso2": "fi", - "dialCode": "358", - "priority": 0, - "areaCodes": null - }, - { - "name": "France", - "iso2": "fr", - "dialCode": "33", - "priority": 0, - "areaCodes": null - }, - { - "name": "French Guiana (Guyane française)", - "iso2": "gf", - "dialCode": "594", - "priority": 0, - "areaCodes": null - }, - { - "name": "French Polynesia (Polynésie française)", - "iso2": "pf", - "dialCode": "689", - "priority": 0, - "areaCodes": null - }, - { - "name": "Gabon", - "iso2": "ga", - "dialCode": "241", - "priority": 0, - "areaCodes": null - }, - { - "name": "Gambia", - "iso2": "gm", - "dialCode": "220", - "priority": 0, - "areaCodes": null - }, - { - "name": "Georgia (საქართველო)", - "iso2": "ge", - "dialCode": "995", - "priority": 0, - "areaCodes": null - }, - { - "name": "Germany (Deutschland)", - "iso2": "de", - "dialCode": "49", - "priority": 0, - "areaCodes": null - }, - { - "name": "Ghana (Gaana)", - "iso2": "gh", - "dialCode": "233", - "priority": 0, - "areaCodes": null - }, - { - "name": "Gibraltar", - "iso2": "gi", - "dialCode": "350", - "priority": 0, - "areaCodes": null - }, - { - "name": "Greece (Ελλάδα)", - "iso2": "gr", - "dialCode": "30", - "priority": 0, - "areaCodes": null - }, - { - "name": "Greenland (Kalaallit Nunaat)", - "iso2": "gl", - "dialCode": "299", - "priority": 0, - "areaCodes": null - }, - { - "name": "Grenada", - "iso2": "gd", - "dialCode": "1473", - "priority": 0, - "areaCodes": null - }, - { - "name": "Guadeloupe", - "iso2": "gp", - "dialCode": "590", - "priority": 0, - "areaCodes": null - }, - { - "name": "Guam", - "iso2": "gu", - "dialCode": "1671", - "priority": 0, - "areaCodes": null - }, - { - "name": "Guatemala", - "iso2": "gt", - "dialCode": "502", - "priority": 0, - "areaCodes": null - }, - { - "name": "Guernsey", - "iso2": "gg", - "dialCode": "44", - "priority": 1, - "areaCodes": null - }, - { - "name": "Guinea (Guinée)", - "iso2": "gn", - "dialCode": "224", - "priority": 0, - "areaCodes": null - }, - { - "name": "Guinea-Bissau (Guiné Bissau)", - "iso2": "gw", - "dialCode": "245", - "priority": 0, - "areaCodes": null - }, - { - "name": "Guyana", - "iso2": "gy", - "dialCode": "592", - "priority": 0, - "areaCodes": null - }, - { - "name": "Haiti", - "iso2": "ht", - "dialCode": "509", - "priority": 0, - "areaCodes": null - }, - { - "name": "Honduras", - "iso2": "hn", - "dialCode": "504", - "priority": 0, - "areaCodes": null - }, - { - "name": "Hong Kong (香港)", - "iso2": "hk", - "dialCode": "852", - "priority": 0, - "areaCodes": null - }, - { - "name": "Hungary (Magyarország)", - "iso2": "hu", - "dialCode": "36", - "priority": 0, - "areaCodes": null - }, - { - "name": "Iceland (Ísland)", - "iso2": "is", - "dialCode": "354", - "priority": 0, - "areaCodes": null - }, - { - "name": "India (भारत)", - "iso2": "in", - "dialCode": "91", - "priority": 0, - "areaCodes": null - }, - { - "name": "Indonesia", - "iso2": "id", - "dialCode": "62", - "priority": 0, - "areaCodes": null - }, - { - "name": "Iran (‫ایران‬‎)", - "iso2": "ir", - "dialCode": "98", - "priority": 0, - "areaCodes": null - }, - { - "name": "Iraq (‫العراق‬‎)", - "iso2": "iq", - "dialCode": "964", - "priority": 0, - "areaCodes": null - }, - { - "name": "Ireland", - "iso2": "ie", - "dialCode": "353", - "priority": 0, - "areaCodes": null - }, - { - "name": "Isle of Man", - "iso2": "im", - "dialCode": "44", - "priority": 2, - "areaCodes": null - }, - { - "name": "Israel (‫ישראל‬‎)", - "iso2": "il", - "dialCode": "972", - "priority": 0, - "areaCodes": null - }, - { - "name": "Italy (Italia)", - "iso2": "it", - "dialCode": "39", - "priority": 0, - "areaCodes": null - }, - { - "name": "Jamaica", - "iso2": "jm", - "dialCode": "1876", - "priority": 0, - "areaCodes": null - }, - { - "name": "Japan (日本)", - "iso2": "jp", - "dialCode": "81", - "priority": 0, - "areaCodes": null - }, - { - "name": "Jersey", - "iso2": "je", - "dialCode": "44", - "priority": 3, - "areaCodes": null - }, - { - "name": "Jordan (‫الأردن‬‎)", - "iso2": "jo", - "dialCode": "962", - "priority": 0, - "areaCodes": null - }, - { - "name": "Kazakhstan (Казахстан)", - "iso2": "kz", - "dialCode": "7", - "priority": 1, - "areaCodes": null - }, - { - "name": "Kenya", - "iso2": "ke", - "dialCode": "254", - "priority": 0, - "areaCodes": null - }, - { - "name": "Kiribati", - "iso2": "ki", - "dialCode": "686", - "priority": 0, - "areaCodes": null - }, - { - "name": "Kuwait (‫الكويت‬‎)", - "iso2": "kw", - "dialCode": "965", - "priority": 0, - "areaCodes": null - }, - { - "name": "Kyrgyzstan (Кыргызстан)", - "iso2": "kg", - "dialCode": "996", - "priority": 0, - "areaCodes": null - }, - { - "name": "Laos (ລາວ)", - "iso2": "la", - "dialCode": "856", - "priority": 0, - "areaCodes": null - }, - { - "name": "Latvia (Latvija)", - "iso2": "lv", - "dialCode": "371", - "priority": 0, - "areaCodes": null - }, - { - "name": "Lebanon (‫لبنان‬‎)", - "iso2": "lb", - "dialCode": "961", - "priority": 0, - "areaCodes": null - }, - { - "name": "Lesotho", - "iso2": "ls", - "dialCode": "266", - "priority": 0, - "areaCodes": null - }, - { - "name": "Liberia", - "iso2": "lr", - "dialCode": "231", - "priority": 0, - "areaCodes": null - }, - { - "name": "Libya (‫ليبيا‬‎)", - "iso2": "ly", - "dialCode": "218", - "priority": 0, - "areaCodes": null - }, - { - "name": "Liechtenstein", - "iso2": "li", - "dialCode": "423", - "priority": 0, - "areaCodes": null - }, - { - "name": "Lithuania (Lietuva)", - "iso2": "lt", - "dialCode": "370", - "priority": 0, - "areaCodes": null - }, - { - "name": "Luxembourg", - "iso2": "lu", - "dialCode": "352", - "priority": 0, - "areaCodes": null - }, - { - "name": "Macau (澳門)", - "iso2": "mo", - "dialCode": "853", - "priority": 0, - "areaCodes": null - }, - { - "name": "Macedonia (FYROM) (Македонија)", - "iso2": "mk", - "dialCode": "389", - "priority": 0, - "areaCodes": null - }, - { - "name": "Madagascar (Madagasikara)", - "iso2": "mg", - "dialCode": "261", - "priority": 0, - "areaCodes": null - }, - { - "name": "Malawi", - "iso2": "mw", - "dialCode": "265", - "priority": 0, - "areaCodes": null - }, - { - "name": "Malaysia", - "iso2": "my", - "dialCode": "60", - "priority": 0, - "areaCodes": null - }, - { - "name": "Maldives", - "iso2": "mv", - "dialCode": "960", - "priority": 0, - "areaCodes": null - }, - { - "name": "Mali", - "iso2": "ml", - "dialCode": "223", - "priority": 0, - "areaCodes": null - }, - { - "name": "Malta", - "iso2": "mt", - "dialCode": "356", - "priority": 0, - "areaCodes": null - }, - { - "name": "Marshall Islands", - "iso2": "mh", - "dialCode": "692", - "priority": 0, - "areaCodes": null - }, - { - "name": "Martinique", - "iso2": "mq", - "dialCode": "596", - "priority": 0, - "areaCodes": null - }, - { - "name": "Mauritania (‫موريتانيا‬‎)", - "iso2": "mr", - "dialCode": "222", - "priority": 0, - "areaCodes": null - }, - { - "name": "Mauritius (Moris)", - "iso2": "mu", - "dialCode": "230", - "priority": 0, - "areaCodes": null - }, - { - "name": "Mayotte", - "iso2": "yt", - "dialCode": "262", - "priority": 1, - "areaCodes": null - }, - { - "name": "Mexico (México)", - "iso2": "mx", - "dialCode": "52", - "priority": 0, - "areaCodes": null - }, - { - "name": "Micronesia", - "iso2": "fm", - "dialCode": "691", - "priority": 0, - "areaCodes": null - }, - { - "name": "Moldova (Republica Moldova)", - "iso2": "md", - "dialCode": "373", - "priority": 0, - "areaCodes": null - }, - { - "name": "Monaco", - "iso2": "mc", - "dialCode": "377", - "priority": 0, - "areaCodes": null - }, - { - "name": "Mongolia (Монгол)", - "iso2": "mn", - "dialCode": "976", - "priority": 0, - "areaCodes": null - }, - { - "name": "Montenegro (Crna Gora)", - "iso2": "me", - "dialCode": "382", - "priority": 0, - "areaCodes": null - }, - { - "name": "Montserrat", - "iso2": "ms", - "dialCode": "1664", - "priority": 0, - "areaCodes": null - }, - { - "name": "Morocco (‫المغرب‬‎)", - "iso2": "ma", - "dialCode": "212", - "priority": 0, - "areaCodes": null - }, - { - "name": "Mozambique (Moçambique)", - "iso2": "mz", - "dialCode": "258", - "priority": 0, - "areaCodes": null - }, - { - "name": "Myanmar (Burma)", - "iso2": "mm", - "dialCode": "95", - "priority": 0, - "areaCodes": null - }, - { - "name": "Namibia (Namibië)", - "iso2": "na", - "dialCode": "264", - "priority": 0, - "areaCodes": null - }, - { - "name": "Nauru", - "iso2": "nr", - "dialCode": "674", - "priority": 0, - "areaCodes": null - }, - { - "name": "Nepal (नेपाल)", - "iso2": "np", - "dialCode": "977", - "priority": 0, - "areaCodes": null - }, - { - "name": "Netherlands (Nederland)", - "iso2": "nl", - "dialCode": "31", - "priority": 0, - "areaCodes": null - }, - { - "name": "New Caledonia (Nouvelle-Calédonie)", - "iso2": "nc", - "dialCode": "687", - "priority": 0, - "areaCodes": null - }, - { - "name": "New Zealand", - "iso2": "nz", - "dialCode": "64", - "priority": 0, - "areaCodes": null - }, - { - "name": "Nicaragua", - "iso2": "ni", - "dialCode": "505", - "priority": 0, - "areaCodes": null - }, - { - "name": "Niger (Nijar)", - "iso2": "ne", - "dialCode": "227", - "priority": 0, - "areaCodes": null - }, - { - "name": "Nigeria", - "iso2": "ng", - "dialCode": "234", - "priority": 0, - "areaCodes": null - }, - { - "name": "Niue", - "iso2": "nu", - "dialCode": "683", - "priority": 0, - "areaCodes": null - }, - { - "name": "Norfolk Island", - "iso2": "nf", - "dialCode": "672", - "priority": 0, - "areaCodes": null - }, - { - "name": "North Korea (조선 민주주의 인민 공화국)", - "iso2": "kp", - "dialCode": "850", - "priority": 0, - "areaCodes": null - }, - { - "name": "Northern Mariana Islands", - "iso2": "mp", - "dialCode": "1670", - "priority": 0, - "areaCodes": null - }, - { - "name": "Norway (Norge)", - "iso2": "no", - "dialCode": "47", - "priority": 0, - "areaCodes": null - }, - { - "name": "Oman (‫عُمان‬‎)", - "iso2": "om", - "dialCode": "968", - "priority": 0, - "areaCodes": null - }, - { - "name": "Pakistan (‫پاکستان‬‎)", - "iso2": "pk", - "dialCode": "92", - "priority": 0, - "areaCodes": null - }, - { - "name": "Palau", - "iso2": "pw", - "dialCode": "680", - "priority": 0, - "areaCodes": null - }, - { - "name": "Palestine (‫فلسطين‬‎)", - "iso2": "ps", - "dialCode": "970", - "priority": 0, - "areaCodes": null - }, - { - "name": "Panama (Panamá)", - "iso2": "pa", - "dialCode": "507", - "priority": 0, - "areaCodes": null - }, - { - "name": "Papua New Guinea", - "iso2": "pg", - "dialCode": "675", - "priority": 0, - "areaCodes": null - }, - { - "name": "Paraguay", - "iso2": "py", - "dialCode": "595", - "priority": 0, - "areaCodes": null - }, - { - "name": "Peru (Perú)", - "iso2": "pe", - "dialCode": "51", - "priority": 0, - "areaCodes": null - }, - { - "name": "Philippines", - "iso2": "ph", - "dialCode": "63", - "priority": 0, - "areaCodes": null - }, - { - "name": "Poland (Polska)", - "iso2": "pl", - "dialCode": "48", - "priority": 0, - "areaCodes": null - }, - { - "name": "Portugal", - "iso2": "pt", - "dialCode": "351", - "priority": 0, - "areaCodes": null - }, - { - "name": "Puerto Rico", - "iso2": "pr", - "dialCode": "1", - "priority": 3, - "areaCodes": [ - "787", - "939" - ] - }, - { - "name": "Qatar (‫قطر‬‎)", - "iso2": "qa", - "dialCode": "974", - "priority": 0, - "areaCodes": null - }, - { - "name": "Réunion (La Réunion)", - "iso2": "re", - "dialCode": "262", - "priority": 0, - "areaCodes": null - }, - { - "name": "Romania (România)", - "iso2": "ro", - "dialCode": "40", - "priority": 0, - "areaCodes": null - }, - { - "name": "Russia (Россия)", - "iso2": "ru", - "dialCode": "7", - "priority": 0, - "areaCodes": null - }, - { - "name": "Rwanda", - "iso2": "rw", - "dialCode": "250", - "priority": 0, - "areaCodes": null - }, - { - "name": "Saint Barthélemy (Saint-Barthélemy)", - "iso2": "bl", - "dialCode": "590", - "priority": 1, - "areaCodes": null - }, - { - "name": "Saint Helena", - "iso2": "sh", - "dialCode": "290", - "priority": 0, - "areaCodes": null - }, - { - "name": "Saint Kitts and Nevis", - "iso2": "kn", - "dialCode": "1869", - "priority": 0, - "areaCodes": null - }, - { - "name": "Saint Lucia", - "iso2": "lc", - "dialCode": "1758", - "priority": 0, - "areaCodes": null - }, - { - "name": "Saint Martin (Saint-Martin (partie française))", - "iso2": "mf", - "dialCode": "590", - "priority": 2, - "areaCodes": null - }, - { - "name": "Saint Pierre and Miquelon (Saint-Pierre-et-Miquelon)", - "iso2": "pm", - "dialCode": "508", - "priority": 0, - "areaCodes": null - }, - { - "name": "Saint Vincent and the Grenadines", - "iso2": "vc", - "dialCode": "1784", - "priority": 0, - "areaCodes": null - }, - { - "name": "Samoa", - "iso2": "ws", - "dialCode": "685", - "priority": 0, - "areaCodes": null - }, - { - "name": "San Marino", - "iso2": "sm", - "dialCode": "378", - "priority": 0, - "areaCodes": null - }, - { - "name": "São Tomé and Príncipe (São Tomé e Príncipe)", - "iso2": "st", - "dialCode": "239", - "priority": 0, - "areaCodes": null - }, - { - "name": "Saudi Arabia (‫المملكة العربية السعودية‬‎)", - "iso2": "sa", - "dialCode": "966", - "priority": 0, - "areaCodes": null - }, - { - "name": "Senegal (Sénégal)", - "iso2": "sn", - "dialCode": "221", - "priority": 0, - "areaCodes": null - }, - { - "name": "Serbia (Србија)", - "iso2": "rs", - "dialCode": "381", - "priority": 0, - "areaCodes": null - }, - { - "name": "Seychelles", - "iso2": "sc", - "dialCode": "248", - "priority": 0, - "areaCodes": null - }, - { - "name": "Sierra Leone", - "iso2": "sl", - "dialCode": "232", - "priority": 0, - "areaCodes": null - }, - { - "name": "Singapore", - "iso2": "sg", - "dialCode": "65", - "priority": 0, - "areaCodes": null - }, - { - "name": "Sint Maarten", - "iso2": "sx", - "dialCode": "1721", - "priority": 0, - "areaCodes": null - }, - { - "name": "Slovakia (Slovensko)", - "iso2": "sk", - "dialCode": "421", - "priority": 0, - "areaCodes": null - }, - { - "name": "Slovenia (Slovenija)", - "iso2": "si", - "dialCode": "386", - "priority": 0, - "areaCodes": null - }, - { - "name": "Solomon Islands", - "iso2": "sb", - "dialCode": "677", - "priority": 0, - "areaCodes": null - }, - { - "name": "Somalia (Soomaaliya)", - "iso2": "so", - "dialCode": "252", - "priority": 0, - "areaCodes": null - }, - { - "name": "South Africa", - "iso2": "za", - "dialCode": "27", - "priority": 0, - "areaCodes": null - }, - { - "name": "South Korea (대한민국)", - "iso2": "kr", - "dialCode": "82", - "priority": 0, - "areaCodes": null - }, - { - "name": "South Sudan (‫جنوب السودان‬‎)", - "iso2": "ss", - "dialCode": "211", - "priority": 0, - "areaCodes": null - }, - { - "name": "Spain (España)", - "iso2": "es", - "dialCode": "34", - "priority": 0, - "areaCodes": null - }, - { - "name": "Sri Lanka (ශ්‍රී ලංකාව)", - "iso2": "lk", - "dialCode": "94", - "priority": 0, - "areaCodes": null - }, - { - "name": "Sudan (‫السودان‬‎)", - "iso2": "sd", - "dialCode": "249", - "priority": 0, - "areaCodes": null - }, - { - "name": "Suriname", - "iso2": "sr", - "dialCode": "597", - "priority": 0, - "areaCodes": null - }, - { - "name": "Svalbard and Jan Mayen", - "iso2": "sj", - "dialCode": "47", - "priority": 1, - "areaCodes": null - }, - { - "name": "Swaziland", - "iso2": "sz", - "dialCode": "268", - "priority": 0, - "areaCodes": null - }, - { - "name": "Sweden (Sverige)", - "iso2": "se", - "dialCode": "46", - "priority": 0, - "areaCodes": null - }, - { - "name": "Switzerland (Schweiz)", - "iso2": "ch", - "dialCode": "41", - "priority": 0, - "areaCodes": null - }, - { - "name": "Syria (‫سوريا‬‎)", - "iso2": "sy", - "dialCode": "963", - "priority": 0, - "areaCodes": null - }, - { - "name": "Taiwan (台灣)", - "iso2": "tw", - "dialCode": "886", - "priority": 0, - "areaCodes": null - }, - { - "name": "Tajikistan", - "iso2": "tj", - "dialCode": "992", - "priority": 0, - "areaCodes": null - }, - { - "name": "Tanzania", - "iso2": "tz", - "dialCode": "255", - "priority": 0, - "areaCodes": null - }, - { - "name": "Thailand (ไทย)", - "iso2": "th", - "dialCode": "66", - "priority": 0, - "areaCodes": null - }, - { - "name": "Timor-Leste", - "iso2": "tl", - "dialCode": "670", - "priority": 0, - "areaCodes": null - }, - { - "name": "Togo", - "iso2": "tg", - "dialCode": "228", - "priority": 0, - "areaCodes": null - }, - { - "name": "Tokelau", - "iso2": "tk", - "dialCode": "690", - "priority": 0, - "areaCodes": null - }, - { - "name": "Tonga", - "iso2": "to", - "dialCode": "676", - "priority": 0, - "areaCodes": null - }, - { - "name": "Trinidad and Tobago", - "iso2": "tt", - "dialCode": "1868", - "priority": 0, - "areaCodes": null - }, - { - "name": "Tunisia (‫تونس‬‎)", - "iso2": "tn", - "dialCode": "216", - "priority": 0, - "areaCodes": null - }, - { - "name": "Turkey (Türkiye)", - "iso2": "tr", - "dialCode": "90", - "priority": 0, - "areaCodes": null - }, - { - "name": "Turkmenistan", - "iso2": "tm", - "dialCode": "993", - "priority": 0, - "areaCodes": null - }, - { - "name": "Turks and Caicos Islands", - "iso2": "tc", - "dialCode": "1649", - "priority": 0, - "areaCodes": null - }, - { - "name": "Tuvalu", - "iso2": "tv", - "dialCode": "688", - "priority": 0, - "areaCodes": null - }, - { - "name": "U.S. Virgin Islands", - "iso2": "vi", - "dialCode": "1340", - "priority": 0, - "areaCodes": null - }, - { - "name": "Uganda", - "iso2": "ug", - "dialCode": "256", - "priority": 0, - "areaCodes": null - }, - { - "name": "Ukraine (Україна)", - "iso2": "ua", - "dialCode": "380", - "priority": 0, - "areaCodes": null - }, - { - "name": "United Arab Emirates (‫الإمارات العربية المتحدة‬‎)", - "iso2": "ae", - "dialCode": "971", - "priority": 0, - "areaCodes": null - }, - { - "name": "United Kingdom", - "iso2": "gb", - "dialCode": "44", - "priority": 0, - "areaCodes": null - }, - { - "name": "United States", - "iso2": "us", - "dialCode": "1", - "priority": 0, - "areaCodes": null - }, - { - "name": "Uruguay", - "iso2": "uy", - "dialCode": "598", - "priority": 0, - "areaCodes": null - }, - { - "name": "Uzbekistan (Oʻzbekiston)", - "iso2": "uz", - "dialCode": "998", - "priority": 0, - "areaCodes": null - }, - { - "name": "Vanuatu", - "iso2": "vu", - "dialCode": "678", - "priority": 0, - "areaCodes": null - }, - { - "name": "Vatican City (Città del Vaticano)", - "iso2": "va", - "dialCode": "39", - "priority": 1, - "areaCodes": null - }, - { - "name": "Venezuela", - "iso2": "ve", - "dialCode": "58", - "priority": 0, - "areaCodes": null - }, - { - "name": "Vietnam (Việt Nam)", - "iso2": "vn", - "dialCode": "84", - "priority": 0, - "areaCodes": null - }, - { - "name": "Wallis and Futuna", - "iso2": "wf", - "dialCode": "681", - "priority": 0, - "areaCodes": null - }, - { - "name": "Western Sahara (‫الصحراء الغربية‬‎)", - "iso2": "eh", - "dialCode": "212", - "priority": 1, - "areaCodes": null - }, - { - "name": "Yemen (‫اليمن‬‎)", - "iso2": "ye", - "dialCode": "967", - "priority": 0, - "areaCodes": null - }, - { - "name": "Zambia", - "iso2": "zm", - "dialCode": "260", - "priority": 0, - "areaCodes": null - }, - { - "name": "Zimbabwe", - "iso2": "zw", - "dialCode": "263", - "priority": 0, - "areaCodes": null - }, - { - "name": "Åland Islands", - "iso2": "ax", - "dialCode": "358", - "priority": 1, - "areaCodes": null - } -] \ No newline at end of file diff --git a/app/src/main/res/raw/node_modules_reactnativephoneinput_lib_resources_numbertype.json b/app/src/main/res/raw/node_modules_reactnativephoneinput_lib_resources_numbertype.json deleted file mode 100644 index 533f3f1f..00000000 --- a/app/src/main/res/raw/node_modules_reactnativephoneinput_lib_resources_numbertype.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "FIXED_LINE": 0, - "MOBILE": 1, - "FIXED_LINE_OR_MOBILE": 2, - "TOLL_FREE": 3, - "PREMIUM_RATE": 4, - "SHARED_COST": 5, - "VOIP": 6, - "PERSONAL_NUMBER": 7, - "PAGER": 8, - "UAN": 9, - "VOICEMAIL": 10, - "UNKNOWN": -1 -} diff --git a/app/src/main/res/raw/node_modules_reactnativevectoricons_glyphmaps_feather.json b/app/src/main/res/raw/node_modules_reactnativevectoricons_glyphmaps_feather.json deleted file mode 100644 index c17b44b8..00000000 --- a/app/src/main/res/raw/node_modules_reactnativevectoricons_glyphmaps_feather.json +++ /dev/null @@ -1,281 +0,0 @@ -{ - "activity": 61696, - "airplay": 61697, - "alert-circle": 61698, - "alert-octagon": 61699, - "alert-triangle": 61700, - "align-center": 61701, - "align-justify": 61702, - "align-left": 61703, - "align-right": 61704, - "anchor": 61705, - "aperture": 61706, - "archive": 61707, - "arrow-down": 61708, - "arrow-down-circle": 61709, - "arrow-down-left": 61710, - "arrow-down-right": 61711, - "arrow-left": 61712, - "arrow-left-circle": 61713, - "arrow-right": 61714, - "arrow-right-circle": 61715, - "arrow-up": 61716, - "arrow-up-circle": 61717, - "arrow-up-left": 61718, - "arrow-up-right": 61719, - "at-sign": 61720, - "award": 61721, - "bar-chart": 61722, - "bar-chart-2": 61723, - "battery": 61724, - "battery-charging": 61725, - "bell": 61726, - "bell-off": 61727, - "bluetooth": 61728, - "bold": 61729, - "book": 61730, - "book-open": 61731, - "bookmark": 61732, - "box": 61733, - "briefcase": 61734, - "calendar": 61735, - "camera": 61736, - "camera-off": 61737, - "cast": 61738, - "check": 61739, - "check-circle": 61740, - "check-square": 61741, - "chevron-down": 61742, - "chevron-left": 61743, - "chevron-right": 61744, - "chevron-up": 61745, - "chevrons-down": 61746, - "chevrons-left": 61747, - "chevrons-right": 61748, - "chevrons-up": 61749, - "chrome": 61750, - "circle": 61751, - "clipboard": 61752, - "clock": 61753, - "cloud": 61754, - "cloud-drizzle": 61755, - "cloud-lightning": 61756, - "cloud-off": 61757, - "cloud-rain": 61758, - "cloud-snow": 61759, - "code": 61760, - "codepen": 61761, - "codesandbox": 61972, - "coffee": 61762, - "columns": 61973, - "command": 61763, - "compass": 61764, - "copy": 61765, - "corner-down-left": 61766, - "corner-down-right": 61767, - "corner-left-down": 61768, - "corner-left-up": 61769, - "corner-right-down": 61770, - "corner-right-up": 61771, - "corner-up-left": 61772, - "corner-up-right": 61773, - "cpu": 61774, - "credit-card": 61775, - "crop": 61776, - "crosshair": 61777, - "database": 61778, - "delete": 61779, - "disc": 61780, - "dollar-sign": 61781, - "download": 61782, - "download-cloud": 61783, - "droplet": 61784, - "edit": 61785, - "edit-2": 61786, - "edit-3": 61787, - "external-link": 61788, - "eye": 61789, - "eye-off": 61790, - "facebook": 61791, - "fast-forward": 61792, - "feather": 61793, - "figma": 61970, - "file": 61794, - "file-minus": 61795, - "file-plus": 61796, - "file-text": 61797, - "film": 61798, - "filter": 61799, - "flag": 61800, - "folder": 61801, - "folder-minus": 61802, - "folder-plus": 61803, - "frown": 61804, - "gift": 61805, - "git-branch": 61806, - "git-commit": 61807, - "git-merge": 61808, - "git-pull-request": 61809, - "github": 61810, - "gitlab": 61811, - "globe": 61812, - "grid": 61813, - "hard-drive": 61814, - "hash": 61815, - "headphones": 61816, - "heart": 61817, - "help-circle": 61818, - "hexagon": 61974, - "home": 61819, - "image": 61820, - "inbox": 61821, - "info": 61822, - "instagram": 61823, - "italic": 61824, - "key": 61967, - "layers": 61825, - "layout": 61826, - "life-buoy": 61827, - "link": 61828, - "link-2": 61829, - "linkedin": 61830, - "list": 61831, - "loader": 61832, - "lock": 61833, - "log-in": 61834, - "log-out": 61835, - "mail": 61836, - "map": 61837, - "map-pin": 61838, - "maximize": 61839, - "maximize-2": 61840, - "meh": 61841, - "menu": 61842, - "message-circle": 61843, - "message-square": 61844, - "mic": 61845, - "mic-off": 61846, - "minimize": 61847, - "minimize-2": 61848, - "minus": 61849, - "minus-circle": 61850, - "minus-square": 61851, - "monitor": 61852, - "moon": 61853, - "more-horizontal": 61854, - "more-vertical": 61855, - "mouse-pointer": 61968, - "move": 61856, - "music": 61857, - "navigation": 61858, - "navigation-2": 61859, - "octagon": 61860, - "package": 61861, - "paperclip": 61862, - "pause": 61863, - "pause-circle": 61864, - "pen-tool": 61969, - "percent": 61865, - "phone": 61866, - "phone-call": 61867, - "phone-forwarded": 61868, - "phone-incoming": 61869, - "phone-missed": 61870, - "phone-off": 61871, - "phone-outgoing": 61872, - "pie-chart": 61873, - "play": 61874, - "play-circle": 61875, - "plus": 61876, - "plus-circle": 61877, - "plus-square": 61878, - "pocket": 61879, - "power": 61880, - "printer": 61881, - "radio": 61882, - "refresh-ccw": 61883, - "refresh-cw": 61884, - "repeat": 61885, - "rewind": 61886, - "rotate-ccw": 61887, - "rotate-cw": 61888, - "rss": 61889, - "save": 61890, - "scissors": 61891, - "search": 61892, - "send": 61893, - "server": 61894, - "settings": 61895, - "share": 61896, - "share-2": 61897, - "shield": 61898, - "shield-off": 61899, - "shopping-bag": 61900, - "shopping-cart": 61901, - "shuffle": 61902, - "sidebar": 61903, - "skip-back": 61904, - "skip-forward": 61905, - "slack": 61906, - "slash": 61907, - "sliders": 61908, - "smartphone": 61909, - "smile": 61910, - "speaker": 61911, - "square": 61912, - "star": 61913, - "stop-circle": 61914, - "sun": 61915, - "sunrise": 61916, - "sunset": 61917, - "tablet": 61975, - "tag": 61919, - "target": 61920, - "terminal": 61921, - "thermometer": 61922, - "thumbs-down": 61923, - "thumbs-up": 61924, - "toggle-left": 61925, - "toggle-right": 61926, - "trash": 61927, - "trash-2": 61928, - "trello": 61929, - "trending-down": 61930, - "trending-up": 61931, - "triangle": 61932, - "truck": 61933, - "tv": 61934, - "twitter": 61935, - "type": 61936, - "umbrella": 61937, - "underline": 61938, - "unlock": 61939, - "upload": 61940, - "upload-cloud": 61941, - "user": 61942, - "user-check": 61943, - "user-minus": 61944, - "user-plus": 61945, - "user-x": 61946, - "users": 61947, - "video": 61948, - "video-off": 61949, - "voicemail": 61950, - "volume": 61951, - "volume-1": 61952, - "volume-2": 61953, - "volume-x": 61954, - "watch": 61955, - "wifi": 61956, - "wifi-off": 61957, - "wind": 61958, - "x": 61959, - "x-circle": 61960, - "x-octagon": 61971, - "x-square": 61961, - "youtube": 61962, - "zap": 61963, - "zap-off": 61964, - "zoom-in": 61965, - "zoom-out": 61966 -} \ No newline at end of file diff --git a/app/src/main/res/raw/node_modules_reactnativevectoricons_glyphmaps_fontawesome5free.json b/app/src/main/res/raw/node_modules_reactnativevectoricons_glyphmaps_fontawesome5free.json deleted file mode 100644 index a93ad409..00000000 --- a/app/src/main/res/raw/node_modules_reactnativevectoricons_glyphmaps_fontawesome5free.json +++ /dev/null @@ -1,1352 +0,0 @@ -{ - "500px": 62062, - "accessible-icon": 62312, - "accusoft": 62313, - "acquisitions-incorporated": 63151, - "ad": 63041, - "address-book": 62137, - "address-card": 62139, - "adjust": 61506, - "adn": 61808, - "adobe": 63352, - "adversal": 62314, - "affiliatetheme": 62315, - "air-freshener": 62928, - "algolia": 62316, - "align-center": 61495, - "align-justify": 61497, - "align-left": 61494, - "align-right": 61496, - "alipay": 63042, - "allergies": 62561, - "amazon": 62064, - "amazon-pay": 62508, - "ambulance": 61689, - "american-sign-language-interpreting": 62115, - "amilia": 62317, - "anchor": 61757, - "android": 61819, - "angellist": 61961, - "angle-double-down": 61699, - "angle-double-left": 61696, - "angle-double-right": 61697, - "angle-double-up": 61698, - "angle-down": 61703, - "angle-left": 61700, - "angle-right": 61701, - "angle-up": 61702, - "angry": 62806, - "angrycreative": 62318, - "angular": 62496, - "ankh": 63044, - "app-store": 62319, - "app-store-ios": 62320, - "apper": 62321, - "apple": 61817, - "apple-alt": 62929, - "apple-pay": 62485, - "archive": 61831, - "archway": 62807, - "arrow-alt-circle-down": 62296, - "arrow-alt-circle-left": 62297, - "arrow-alt-circle-right": 62298, - "arrow-alt-circle-up": 62299, - "arrow-circle-down": 61611, - "arrow-circle-left": 61608, - "arrow-circle-right": 61609, - "arrow-circle-up": 61610, - "arrow-down": 61539, - "arrow-left": 61536, - "arrow-right": 61537, - "arrow-up": 61538, - "arrows-alt": 61618, - "arrows-alt-h": 62263, - "arrows-alt-v": 62264, - "artstation": 63354, - "assistive-listening-systems": 62114, - "asterisk": 61545, - "asymmetrik": 62322, - "at": 61946, - "atlas": 62808, - "atlassian": 63355, - "atom": 62930, - "audible": 62323, - "audio-description": 62110, - "autoprefixer": 62492, - "avianex": 62324, - "aviato": 62497, - "award": 62809, - "aws": 62325, - "baby": 63356, - "baby-carriage": 63357, - "backspace": 62810, - "backward": 61514, - "bacon": 63461, - "balance-scale": 62030, - "ban": 61534, - "band-aid": 62562, - "bandcamp": 62165, - "barcode": 61482, - "bars": 61641, - "baseball-ball": 62515, - "basketball-ball": 62516, - "bath": 62157, - "battery-empty": 62020, - "battery-full": 62016, - "battery-half": 62018, - "battery-quarter": 62019, - "battery-three-quarters": 62017, - "bed": 62006, - "beer": 61692, - "behance": 61876, - "behance-square": 61877, - "bell": 61683, - "bell-slash": 61942, - "bezier-curve": 62811, - "bible": 63047, - "bicycle": 61958, - "bimobject": 62328, - "binoculars": 61925, - "biohazard": 63360, - "birthday-cake": 61949, - "bitbucket": 61809, - "bitcoin": 62329, - "bity": 62330, - "black-tie": 62078, - "blackberry": 62331, - "blender": 62743, - "blender-phone": 63158, - "blind": 62109, - "blog": 63361, - "blogger": 62332, - "blogger-b": 62333, - "bluetooth": 62099, - "bluetooth-b": 62100, - "bold": 61490, - "bolt": 61671, - "bomb": 61922, - "bone": 62935, - "bong": 62812, - "book": 61485, - "book-dead": 63159, - "book-medical": 63462, - "book-open": 62744, - "book-reader": 62938, - "bookmark": 61486, - "bowling-ball": 62518, - "box": 62566, - "box-open": 62622, - "boxes": 62568, - "braille": 62113, - "brain": 62940, - "bread-slice": 63468, - "briefcase": 61617, - "briefcase-medical": 62569, - "broadcast-tower": 62745, - "broom": 62746, - "brush": 62813, - "btc": 61786, - "bug": 61832, - "building": 61869, - "bullhorn": 61601, - "bullseye": 61760, - "burn": 62570, - "buromobelexperte": 62335, - "bus": 61959, - "bus-alt": 62814, - "business-time": 63050, - "buysellads": 61965, - "calculator": 61932, - "calendar": 61747, - "calendar-alt": 61555, - "calendar-check": 62068, - "calendar-day": 63363, - "calendar-minus": 62066, - "calendar-plus": 62065, - "calendar-times": 62067, - "calendar-week": 63364, - "camera": 61488, - "camera-retro": 61571, - "campground": 63163, - "canadian-maple-leaf": 63365, - "candy-cane": 63366, - "cannabis": 62815, - "capsules": 62571, - "car": 61881, - "car-alt": 62942, - "car-battery": 62943, - "car-crash": 62945, - "car-side": 62948, - "caret-down": 61655, - "caret-left": 61657, - "caret-right": 61658, - "caret-square-down": 61776, - "caret-square-left": 61841, - "caret-square-right": 61778, - "caret-square-up": 61777, - "caret-up": 61656, - "carrot": 63367, - "cart-arrow-down": 61976, - "cart-plus": 61975, - "cash-register": 63368, - "cat": 63166, - "cc-amazon-pay": 62509, - "cc-amex": 61939, - "cc-apple-pay": 62486, - "cc-diners-club": 62028, - "cc-discover": 61938, - "cc-jcb": 62027, - "cc-mastercard": 61937, - "cc-paypal": 61940, - "cc-stripe": 61941, - "cc-visa": 61936, - "centercode": 62336, - "centos": 63369, - "certificate": 61603, - "chair": 63168, - "chalkboard": 62747, - "chalkboard-teacher": 62748, - "charging-station": 62951, - "chart-area": 61950, - "chart-bar": 61568, - "chart-line": 61953, - "chart-pie": 61952, - "check": 61452, - "check-circle": 61528, - "check-double": 62816, - "check-square": 61770, - "cheese": 63471, - "chess": 62521, - "chess-bishop": 62522, - "chess-board": 62524, - "chess-king": 62527, - "chess-knight": 62529, - "chess-pawn": 62531, - "chess-queen": 62533, - "chess-rook": 62535, - "chevron-circle-down": 61754, - "chevron-circle-left": 61751, - "chevron-circle-right": 61752, - "chevron-circle-up": 61753, - "chevron-down": 61560, - "chevron-left": 61523, - "chevron-right": 61524, - "chevron-up": 61559, - "child": 61870, - "chrome": 62056, - "church": 62749, - "circle": 61713, - "circle-notch": 61902, - "city": 63055, - "clinic-medical": 63474, - "clipboard": 62248, - "clipboard-check": 62572, - "clipboard-list": 62573, - "clock": 61463, - "clone": 62029, - "closed-captioning": 61962, - "cloud": 61634, - "cloud-download-alt": 62337, - "cloud-meatball": 63291, - "cloud-moon": 63171, - "cloud-moon-rain": 63292, - "cloud-rain": 63293, - "cloud-showers-heavy": 63296, - "cloud-sun": 63172, - "cloud-sun-rain": 63299, - "cloud-upload-alt": 62338, - "cloudscale": 62339, - "cloudsmith": 62340, - "cloudversify": 62341, - "cocktail": 62817, - "code": 61729, - "code-branch": 61734, - "codepen": 61899, - "codiepie": 62084, - "coffee": 61684, - "cog": 61459, - "cogs": 61573, - "coins": 62750, - "columns": 61659, - "comment": 61557, - "comment-alt": 62074, - "comment-dollar": 63057, - "comment-dots": 62637, - "comment-medical": 63477, - "comment-slash": 62643, - "comments": 61574, - "comments-dollar": 63059, - "compact-disc": 62751, - "compass": 61774, - "compress": 61542, - "compress-arrows-alt": 63372, - "concierge-bell": 62818, - "confluence": 63373, - "connectdevelop": 61966, - "contao": 62061, - "cookie": 62819, - "cookie-bite": 62820, - "copy": 61637, - "copyright": 61945, - "couch": 62648, - "cpanel": 62344, - "creative-commons": 62046, - "creative-commons-by": 62695, - "creative-commons-nc": 62696, - "creative-commons-nc-eu": 62697, - "creative-commons-nc-jp": 62698, - "creative-commons-nd": 62699, - "creative-commons-pd": 62700, - "creative-commons-pd-alt": 62701, - "creative-commons-remix": 62702, - "creative-commons-sa": 62703, - "creative-commons-sampling": 62704, - "creative-commons-sampling-plus": 62705, - "creative-commons-share": 62706, - "creative-commons-zero": 62707, - "credit-card": 61597, - "critical-role": 63177, - "crop": 61733, - "crop-alt": 62821, - "cross": 63060, - "crosshairs": 61531, - "crow": 62752, - "crown": 62753, - "crutch": 63479, - "css3": 61756, - "css3-alt": 62347, - "cube": 61874, - "cubes": 61875, - "cut": 61636, - "cuttlefish": 62348, - "d-and-d": 62349, - "d-and-d-beyond": 63178, - "dashcube": 61968, - "database": 61888, - "deaf": 62116, - "delicious": 61861, - "democrat": 63303, - "deploydog": 62350, - "deskpro": 62351, - "desktop": 61704, - "dev": 63180, - "deviantart": 61885, - "dharmachakra": 63061, - "dhl": 63376, - "diagnoses": 62576, - "diaspora": 63377, - "dice": 62754, - "dice-d20": 63183, - "dice-d6": 63185, - "dice-five": 62755, - "dice-four": 62756, - "dice-one": 62757, - "dice-six": 62758, - "dice-three": 62759, - "dice-two": 62760, - "digg": 61862, - "digital-ocean": 62353, - "digital-tachograph": 62822, - "directions": 62955, - "discord": 62354, - "discourse": 62355, - "divide": 62761, - "dizzy": 62823, - "dna": 62577, - "dochub": 62356, - "docker": 62357, - "dog": 63187, - "dollar-sign": 61781, - "dolly": 62578, - "dolly-flatbed": 62580, - "donate": 62649, - "door-closed": 62762, - "door-open": 62763, - "dot-circle": 61842, - "dove": 62650, - "download": 61465, - "draft2digital": 62358, - "drafting-compass": 62824, - "dragon": 63189, - "draw-polygon": 62958, - "dribbble": 61821, - "dribbble-square": 62359, - "dropbox": 61803, - "drum": 62825, - "drum-steelpan": 62826, - "drumstick-bite": 63191, - "drupal": 61865, - "dumbbell": 62539, - "dumpster": 63379, - "dumpster-fire": 63380, - "dungeon": 63193, - "dyalog": 62361, - "earlybirds": 62362, - "ebay": 62708, - "edge": 62082, - "edit": 61508, - "egg": 63483, - "eject": 61522, - "elementor": 62512, - "ellipsis-h": 61761, - "ellipsis-v": 61762, - "ello": 62961, - "ember": 62499, - "empire": 61905, - "envelope": 61664, - "envelope-open": 62134, - "envelope-open-text": 63064, - "envelope-square": 61849, - "envira": 62105, - "equals": 62764, - "eraser": 61741, - "erlang": 62365, - "ethereum": 62510, - "ethernet": 63382, - "etsy": 62167, - "euro-sign": 61779, - "exchange-alt": 62306, - "exclamation": 61738, - "exclamation-circle": 61546, - "exclamation-triangle": 61553, - "expand": 61541, - "expand-arrows-alt": 62238, - "expeditedssl": 62014, - "external-link-alt": 62301, - "external-link-square-alt": 62304, - "eye": 61550, - "eye-dropper": 61947, - "eye-slash": 61552, - "facebook": 61594, - "facebook-f": 62366, - "facebook-messenger": 62367, - "facebook-square": 61570, - "fantasy-flight-games": 63196, - "fast-backward": 61513, - "fast-forward": 61520, - "fax": 61868, - "feather": 62765, - "feather-alt": 62827, - "fedex": 63383, - "fedora": 63384, - "female": 61826, - "fighter-jet": 61691, - "figma": 63385, - "file": 61787, - "file-alt": 61788, - "file-archive": 61894, - "file-audio": 61895, - "file-code": 61897, - "file-contract": 62828, - "file-csv": 63197, - "file-download": 62829, - "file-excel": 61891, - "file-export": 62830, - "file-image": 61893, - "file-import": 62831, - "file-invoice": 62832, - "file-invoice-dollar": 62833, - "file-medical": 62583, - "file-medical-alt": 62584, - "file-pdf": 61889, - "file-powerpoint": 61892, - "file-prescription": 62834, - "file-signature": 62835, - "file-upload": 62836, - "file-video": 61896, - "file-word": 61890, - "fill": 62837, - "fill-drip": 62838, - "film": 61448, - "filter": 61616, - "fingerprint": 62839, - "fire": 61549, - "fire-alt": 63460, - "fire-extinguisher": 61748, - "firefox": 62057, - "first-aid": 62585, - "first-order": 62128, - "first-order-alt": 62730, - "firstdraft": 62369, - "fish": 62840, - "fist-raised": 63198, - "flag": 61476, - "flag-checkered": 61726, - "flag-usa": 63309, - "flask": 61635, - "flickr": 61806, - "flipboard": 62541, - "flushed": 62841, - "fly": 62487, - "folder": 61563, - "folder-minus": 63069, - "folder-open": 61564, - "folder-plus": 63070, - "font": 61489, - "font-awesome": 62132, - "font-awesome-alt": 62300, - "font-awesome-flag": 62501, - "font-awesome-logo-full": 62694, - "fonticons": 62080, - "fonticons-fi": 62370, - "football-ball": 62542, - "fort-awesome": 62086, - "fort-awesome-alt": 62371, - "forumbee": 61969, - "forward": 61518, - "foursquare": 61824, - "free-code-camp": 62149, - "freebsd": 62372, - "frog": 62766, - "frown": 61721, - "frown-open": 62842, - "fulcrum": 62731, - "funnel-dollar": 63074, - "futbol": 61923, - "galactic-republic": 62732, - "galactic-senate": 62733, - "gamepad": 61723, - "gas-pump": 62767, - "gavel": 61667, - "gem": 62373, - "genderless": 61997, - "get-pocket": 62053, - "gg": 62048, - "gg-circle": 62049, - "ghost": 63202, - "gift": 61547, - "gifts": 63388, - "git": 61907, - "git-square": 61906, - "github": 61595, - "github-alt": 61715, - "github-square": 61586, - "gitkraken": 62374, - "gitlab": 62102, - "gitter": 62502, - "glass-cheers": 63391, - "glass-martini": 61440, - "glass-martini-alt": 62843, - "glass-whiskey": 63392, - "glasses": 62768, - "glide": 62117, - "glide-g": 62118, - "globe": 61612, - "globe-africa": 62844, - "globe-americas": 62845, - "globe-asia": 62846, - "globe-europe": 63394, - "gofore": 62375, - "golf-ball": 62544, - "goodreads": 62376, - "goodreads-g": 62377, - "google": 61856, - "google-drive": 62378, - "google-play": 62379, - "google-plus": 62131, - "google-plus-g": 61653, - "google-plus-square": 61652, - "google-wallet": 61934, - "gopuram": 63076, - "graduation-cap": 61853, - "gratipay": 61828, - "grav": 62166, - "greater-than": 62769, - "greater-than-equal": 62770, - "grimace": 62847, - "grin": 62848, - "grin-alt": 62849, - "grin-beam": 62850, - "grin-beam-sweat": 62851, - "grin-hearts": 62852, - "grin-squint": 62853, - "grin-squint-tears": 62854, - "grin-stars": 62855, - "grin-tears": 62856, - "grin-tongue": 62857, - "grin-tongue-squint": 62858, - "grin-tongue-wink": 62859, - "grin-wink": 62860, - "grip-horizontal": 62861, - "grip-lines": 63396, - "grip-lines-vertical": 63397, - "grip-vertical": 62862, - "gripfire": 62380, - "grunt": 62381, - "guitar": 63398, - "gulp": 62382, - "h-square": 61693, - "hacker-news": 61908, - "hacker-news-square": 62383, - "hackerrank": 62967, - "hamburger": 63493, - "hammer": 63203, - "hamsa": 63077, - "hand-holding": 62653, - "hand-holding-heart": 62654, - "hand-holding-usd": 62656, - "hand-lizard": 62040, - "hand-middle-finger": 63494, - "hand-paper": 62038, - "hand-peace": 62043, - "hand-point-down": 61607, - "hand-point-left": 61605, - "hand-point-right": 61604, - "hand-point-up": 61606, - "hand-pointer": 62042, - "hand-rock": 62037, - "hand-scissors": 62039, - "hand-spock": 62041, - "hands": 62658, - "hands-helping": 62660, - "handshake": 62133, - "hanukiah": 63206, - "hard-hat": 63495, - "hashtag": 62098, - "hat-wizard": 63208, - "haykal": 63078, - "hdd": 61600, - "heading": 61916, - "headphones": 61477, - "headphones-alt": 62863, - "headset": 62864, - "heart": 61444, - "heart-broken": 63401, - "heartbeat": 61982, - "helicopter": 62771, - "highlighter": 62865, - "hiking": 63212, - "hippo": 63213, - "hips": 62546, - "hire-a-helper": 62384, - "history": 61914, - "hockey-puck": 62547, - "holly-berry": 63402, - "home": 61461, - "hooli": 62503, - "hornbill": 62866, - "horse": 63216, - "horse-head": 63403, - "hospital": 61688, - "hospital-alt": 62589, - "hospital-symbol": 62590, - "hot-tub": 62867, - "hotdog": 63503, - "hotel": 62868, - "hotjar": 62385, - "hourglass": 62036, - "hourglass-end": 62035, - "hourglass-half": 62034, - "hourglass-start": 62033, - "house-damage": 63217, - "houzz": 62076, - "hryvnia": 63218, - "html5": 61755, - "hubspot": 62386, - "i-cursor": 62022, - "ice-cream": 63504, - "icicles": 63405, - "id-badge": 62145, - "id-card": 62146, - "id-card-alt": 62591, - "igloo": 63406, - "image": 61502, - "images": 62210, - "imdb": 62168, - "inbox": 61468, - "indent": 61500, - "industry": 62069, - "infinity": 62772, - "info": 61737, - "info-circle": 61530, - "instagram": 61805, - "intercom": 63407, - "internet-explorer": 62059, - "invision": 63408, - "ioxhost": 61960, - "italic": 61491, - "itunes": 62388, - "itunes-note": 62389, - "java": 62692, - "jedi": 63081, - "jedi-order": 62734, - "jenkins": 62390, - "jira": 63409, - "joget": 62391, - "joint": 62869, - "joomla": 61866, - "journal-whills": 63082, - "js": 62392, - "js-square": 62393, - "jsfiddle": 61900, - "kaaba": 63083, - "kaggle": 62970, - "key": 61572, - "keybase": 62709, - "keyboard": 61724, - "keycdn": 62394, - "khanda": 63085, - "kickstarter": 62395, - "kickstarter-k": 62396, - "kiss": 62870, - "kiss-beam": 62871, - "kiss-wink-heart": 62872, - "kiwi-bird": 62773, - "korvue": 62511, - "landmark": 63087, - "language": 61867, - "laptop": 61705, - "laptop-code": 62972, - "laptop-medical": 63506, - "laravel": 62397, - "lastfm": 61954, - "lastfm-square": 61955, - "laugh": 62873, - "laugh-beam": 62874, - "laugh-squint": 62875, - "laugh-wink": 62876, - "layer-group": 62973, - "leaf": 61548, - "leanpub": 61970, - "lemon": 61588, - "less": 62493, - "less-than": 62774, - "less-than-equal": 62775, - "level-down-alt": 62398, - "level-up-alt": 62399, - "life-ring": 61901, - "lightbulb": 61675, - "line": 62400, - "link": 61633, - "linkedin": 61580, - "linkedin-in": 61665, - "linode": 62136, - "linux": 61820, - "lira-sign": 61845, - "list": 61498, - "list-alt": 61474, - "list-ol": 61643, - "list-ul": 61642, - "location-arrow": 61732, - "lock": 61475, - "lock-open": 62401, - "long-arrow-alt-down": 62217, - "long-arrow-alt-left": 62218, - "long-arrow-alt-right": 62219, - "long-arrow-alt-up": 62220, - "low-vision": 62120, - "luggage-cart": 62877, - "lyft": 62403, - "magento": 62404, - "magic": 61648, - "magnet": 61558, - "mail-bulk": 63092, - "mailchimp": 62878, - "male": 61827, - "mandalorian": 62735, - "map": 62073, - "map-marked": 62879, - "map-marked-alt": 62880, - "map-marker": 61505, - "map-marker-alt": 62405, - "map-pin": 62070, - "map-signs": 62071, - "markdown": 62991, - "marker": 62881, - "mars": 61986, - "mars-double": 61991, - "mars-stroke": 61993, - "mars-stroke-h": 61995, - "mars-stroke-v": 61994, - "mask": 63226, - "mastodon": 62710, - "maxcdn": 61750, - "medal": 62882, - "medapps": 62406, - "medium": 62010, - "medium-m": 62407, - "medkit": 61690, - "medrt": 62408, - "meetup": 62176, - "megaport": 62883, - "meh": 61722, - "meh-blank": 62884, - "meh-rolling-eyes": 62885, - "memory": 62776, - "mendeley": 63411, - "menorah": 63094, - "mercury": 61987, - "meteor": 63315, - "microchip": 62171, - "microphone": 61744, - "microphone-alt": 62409, - "microphone-alt-slash": 62777, - "microphone-slash": 61745, - "microscope": 62992, - "microsoft": 62410, - "minus": 61544, - "minus-circle": 61526, - "minus-square": 61766, - "mitten": 63413, - "mix": 62411, - "mixcloud": 62089, - "mizuni": 62412, - "mobile": 61707, - "mobile-alt": 62413, - "modx": 62085, - "monero": 62416, - "money-bill": 61654, - "money-bill-alt": 62417, - "money-bill-wave": 62778, - "money-bill-wave-alt": 62779, - "money-check": 62780, - "money-check-alt": 62781, - "monument": 62886, - "moon": 61830, - "mortar-pestle": 62887, - "mosque": 63096, - "motorcycle": 61980, - "mountain": 63228, - "mouse-pointer": 62021, - "mug-hot": 63414, - "music": 61441, - "napster": 62418, - "neos": 62994, - "network-wired": 63231, - "neuter": 61996, - "newspaper": 61930, - "nimblr": 62888, - "nintendo-switch": 62488, - "node": 62489, - "node-js": 62419, - "not-equal": 62782, - "notes-medical": 62593, - "npm": 62420, - "ns8": 62421, - "nutritionix": 62422, - "object-group": 62023, - "object-ungroup": 62024, - "odnoklassniki": 62051, - "odnoklassniki-square": 62052, - "oil-can": 62995, - "old-republic": 62736, - "om": 63097, - "opencart": 62013, - "openid": 61851, - "opera": 62058, - "optin-monster": 62012, - "osi": 62490, - "otter": 63232, - "outdent": 61499, - "page4": 62423, - "pagelines": 61836, - "pager": 63509, - "paint-brush": 61948, - "paint-roller": 62890, - "palette": 62783, - "palfed": 62424, - "pallet": 62594, - "paper-plane": 61912, - "paperclip": 61638, - "parachute-box": 62669, - "paragraph": 61917, - "parking": 62784, - "passport": 62891, - "pastafarianism": 63099, - "paste": 61674, - "patreon": 62425, - "pause": 61516, - "pause-circle": 62091, - "paw": 61872, - "paypal": 61933, - "peace": 63100, - "pen": 62212, - "pen-alt": 62213, - "pen-fancy": 62892, - "pen-nib": 62893, - "pen-square": 61771, - "pencil-alt": 62211, - "pencil-ruler": 62894, - "penny-arcade": 63236, - "people-carry": 62670, - "pepper-hot": 63510, - "percent": 62101, - "percentage": 62785, - "periscope": 62426, - "person-booth": 63318, - "phabricator": 62427, - "phoenix-framework": 62428, - "phoenix-squadron": 62737, - "phone": 61589, - "phone-slash": 62429, - "phone-square": 61592, - "phone-volume": 62112, - "php": 62551, - "pied-piper": 62126, - "pied-piper-alt": 61864, - "pied-piper-hat": 62693, - "pied-piper-pp": 61863, - "piggy-bank": 62675, - "pills": 62596, - "pinterest": 61650, - "pinterest-p": 62001, - "pinterest-square": 61651, - "pizza-slice": 63512, - "place-of-worship": 63103, - "plane": 61554, - "plane-arrival": 62895, - "plane-departure": 62896, - "play": 61515, - "play-circle": 61764, - "playstation": 62431, - "plug": 61926, - "plus": 61543, - "plus-circle": 61525, - "plus-square": 61694, - "podcast": 62158, - "poll": 63105, - "poll-h": 63106, - "poo": 62206, - "poo-storm": 63322, - "poop": 63001, - "portrait": 62432, - "pound-sign": 61780, - "power-off": 61457, - "pray": 63107, - "praying-hands": 63108, - "prescription": 62897, - "prescription-bottle": 62597, - "prescription-bottle-alt": 62598, - "print": 61487, - "procedures": 62599, - "product-hunt": 62088, - "project-diagram": 62786, - "pushed": 62433, - "puzzle-piece": 61742, - "python": 62434, - "qq": 61910, - "qrcode": 61481, - "question": 61736, - "question-circle": 61529, - "quidditch": 62552, - "quinscape": 62553, - "quora": 62148, - "quote-left": 61709, - "quote-right": 61710, - "quran": 63111, - "r-project": 62711, - "radiation": 63417, - "radiation-alt": 63418, - "rainbow": 63323, - "random": 61556, - "raspberry-pi": 63419, - "ravelry": 62169, - "react": 62491, - "reacteurope": 63325, - "readme": 62677, - "rebel": 61904, - "receipt": 62787, - "recycle": 61880, - "red-river": 62435, - "reddit": 61857, - "reddit-alien": 62081, - "reddit-square": 61858, - "redhat": 63420, - "redo": 61470, - "redo-alt": 62201, - "registered": 62045, - "renren": 61835, - "reply": 62437, - "reply-all": 61730, - "replyd": 62438, - "republican": 63326, - "researchgate": 62712, - "resolving": 62439, - "restroom": 63421, - "retweet": 61561, - "rev": 62898, - "ribbon": 62678, - "ring": 63243, - "road": 61464, - "robot": 62788, - "rocket": 61749, - "rocketchat": 62440, - "rockrms": 62441, - "route": 62679, - "rss": 61598, - "rss-square": 61763, - "ruble-sign": 61784, - "ruler": 62789, - "ruler-combined": 62790, - "ruler-horizontal": 62791, - "ruler-vertical": 62792, - "running": 63244, - "rupee-sign": 61782, - "sad-cry": 62899, - "sad-tear": 62900, - "safari": 62055, - "sass": 62494, - "satellite": 63423, - "satellite-dish": 63424, - "save": 61639, - "schlix": 62442, - "school": 62793, - "screwdriver": 62794, - "scribd": 62090, - "scroll": 63246, - "sd-card": 63426, - "search": 61442, - "search-dollar": 63112, - "search-location": 63113, - "search-minus": 61456, - "search-plus": 61454, - "searchengin": 62443, - "seedling": 62680, - "sellcast": 62170, - "sellsy": 61971, - "server": 62003, - "servicestack": 62444, - "shapes": 63007, - "share": 61540, - "share-alt": 61920, - "share-alt-square": 61921, - "share-square": 61773, - "shekel-sign": 61963, - "shield-alt": 62445, - "ship": 61978, - "shipping-fast": 62603, - "shirtsinbulk": 61972, - "shoe-prints": 62795, - "shopping-bag": 62096, - "shopping-basket": 62097, - "shopping-cart": 61562, - "shopware": 62901, - "shower": 62156, - "shuttle-van": 62902, - "sign": 62681, - "sign-in-alt": 62198, - "sign-language": 62119, - "sign-out-alt": 62197, - "signal": 61458, - "signature": 62903, - "sim-card": 63428, - "simplybuilt": 61973, - "sistrix": 62446, - "sitemap": 61672, - "sith": 62738, - "skating": 63429, - "sketch": 63430, - "skiing": 63433, - "skiing-nordic": 63434, - "skull": 62796, - "skull-crossbones": 63252, - "skyatlas": 61974, - "skype": 61822, - "slack": 61848, - "slack-hash": 62447, - "slash": 63253, - "sleigh": 63436, - "sliders-h": 61918, - "slideshare": 61927, - "smile": 61720, - "smile-beam": 62904, - "smile-wink": 62682, - "smog": 63327, - "smoking": 62605, - "smoking-ban": 62797, - "sms": 63437, - "snapchat": 62123, - "snapchat-ghost": 62124, - "snapchat-square": 62125, - "snowboarding": 63438, - "snowflake": 62172, - "snowman": 63440, - "snowplow": 63442, - "socks": 63126, - "solar-panel": 62906, - "sort": 61660, - "sort-alpha-down": 61789, - "sort-alpha-up": 61790, - "sort-amount-down": 61792, - "sort-amount-up": 61793, - "sort-down": 61661, - "sort-numeric-down": 61794, - "sort-numeric-up": 61795, - "sort-up": 61662, - "soundcloud": 61886, - "sourcetree": 63443, - "spa": 62907, - "space-shuttle": 61847, - "speakap": 62451, - "spider": 63255, - "spinner": 61712, - "splotch": 62908, - "spotify": 61884, - "spray-can": 62909, - "square": 61640, - "square-full": 62556, - "square-root-alt": 63128, - "squarespace": 62910, - "stack-exchange": 61837, - "stack-overflow": 61804, - "stamp": 62911, - "star": 61445, - "star-and-crescent": 63129, - "star-half": 61577, - "star-half-alt": 62912, - "star-of-david": 63130, - "star-of-life": 63009, - "staylinked": 62453, - "steam": 61878, - "steam-square": 61879, - "steam-symbol": 62454, - "step-backward": 61512, - "step-forward": 61521, - "stethoscope": 61681, - "sticker-mule": 62455, - "sticky-note": 62025, - "stop": 61517, - "stop-circle": 62093, - "stopwatch": 62194, - "store": 62798, - "store-alt": 62799, - "strava": 62504, - "stream": 62800, - "street-view": 61981, - "strikethrough": 61644, - "stripe": 62505, - "stripe-s": 62506, - "stroopwafel": 62801, - "studiovinari": 62456, - "stumbleupon": 61860, - "stumbleupon-circle": 61859, - "subscript": 61740, - "subway": 62009, - "suitcase": 61682, - "suitcase-rolling": 62913, - "sun": 61829, - "superpowers": 62173, - "superscript": 61739, - "supple": 62457, - "surprise": 62914, - "suse": 63446, - "swatchbook": 62915, - "swimmer": 62916, - "swimming-pool": 62917, - "synagogue": 63131, - "sync": 61473, - "sync-alt": 62193, - "syringe": 62606, - "table": 61646, - "table-tennis": 62557, - "tablet": 61706, - "tablet-alt": 62458, - "tablets": 62608, - "tachometer-alt": 62461, - "tag": 61483, - "tags": 61484, - "tape": 62683, - "tasks": 61614, - "taxi": 61882, - "teamspeak": 62713, - "teeth": 63022, - "teeth-open": 63023, - "telegram": 62150, - "telegram-plane": 62462, - "temperature-high": 63337, - "temperature-low": 63339, - "tencent-weibo": 61909, - "tenge": 63447, - "terminal": 61728, - "text-height": 61492, - "text-width": 61493, - "th": 61450, - "th-large": 61449, - "th-list": 61451, - "the-red-yeti": 63133, - "theater-masks": 63024, - "themeco": 62918, - "themeisle": 62130, - "thermometer": 62609, - "thermometer-empty": 62155, - "thermometer-full": 62151, - "thermometer-half": 62153, - "thermometer-quarter": 62154, - "thermometer-three-quarters": 62152, - "think-peaks": 63281, - "thumbs-down": 61797, - "thumbs-up": 61796, - "thumbtack": 61581, - "ticket-alt": 62463, - "times": 61453, - "times-circle": 61527, - "tint": 61507, - "tint-slash": 62919, - "tired": 62920, - "toggle-off": 61956, - "toggle-on": 61957, - "toilet": 63448, - "toilet-paper": 63262, - "toolbox": 62802, - "tools": 63449, - "tooth": 62921, - "torah": 63136, - "torii-gate": 63137, - "tractor": 63266, - "trade-federation": 62739, - "trademark": 62044, - "traffic-light": 63031, - "train": 62008, - "tram": 63450, - "transgender": 61988, - "transgender-alt": 61989, - "trash": 61944, - "trash-alt": 62189, - "trash-restore": 63529, - "trash-restore-alt": 63530, - "tree": 61883, - "trello": 61825, - "tripadvisor": 62050, - "trophy": 61585, - "truck": 61649, - "truck-loading": 62686, - "truck-monster": 63035, - "truck-moving": 62687, - "truck-pickup": 63036, - "tshirt": 62803, - "tty": 61924, - "tumblr": 61811, - "tumblr-square": 61812, - "tv": 62060, - "twitch": 61928, - "twitter": 61593, - "twitter-square": 61569, - "typo3": 62507, - "uber": 62466, - "ubuntu": 63455, - "uikit": 62467, - "umbrella": 61673, - "umbrella-beach": 62922, - "underline": 61645, - "undo": 61666, - "undo-alt": 62186, - "uniregistry": 62468, - "universal-access": 62106, - "university": 61852, - "unlink": 61735, - "unlock": 61596, - "unlock-alt": 61758, - "untappd": 62469, - "upload": 61587, - "ups": 63456, - "usb": 62087, - "user": 61447, - "user-alt": 62470, - "user-alt-slash": 62714, - "user-astronaut": 62715, - "user-check": 62716, - "user-circle": 62141, - "user-clock": 62717, - "user-cog": 62718, - "user-edit": 62719, - "user-friends": 62720, - "user-graduate": 62721, - "user-injured": 63272, - "user-lock": 62722, - "user-md": 61680, - "user-minus": 62723, - "user-ninja": 62724, - "user-nurse": 63535, - "user-plus": 62004, - "user-secret": 61979, - "user-shield": 62725, - "user-slash": 62726, - "user-tag": 62727, - "user-tie": 62728, - "user-times": 62005, - "users": 61632, - "users-cog": 62729, - "usps": 63457, - "ussunnah": 62471, - "utensil-spoon": 62181, - "utensils": 62183, - "vaadin": 62472, - "vector-square": 62923, - "venus": 61985, - "venus-double": 61990, - "venus-mars": 61992, - "viacoin": 62007, - "viadeo": 62121, - "viadeo-square": 62122, - "vial": 62610, - "vials": 62611, - "viber": 62473, - "video": 61501, - "video-slash": 62690, - "vihara": 63143, - "vimeo": 62474, - "vimeo-square": 61844, - "vimeo-v": 62077, - "vine": 61898, - "vk": 61833, - "vnv": 62475, - "volleyball-ball": 62559, - "volume-down": 61479, - "volume-mute": 63145, - "volume-off": 61478, - "volume-up": 61480, - "vote-yea": 63346, - "vr-cardboard": 63273, - "vuejs": 62495, - "walking": 62804, - "wallet": 62805, - "warehouse": 62612, - "water": 63347, - "weebly": 62924, - "weibo": 61834, - "weight": 62614, - "weight-hanging": 62925, - "weixin": 61911, - "whatsapp": 62002, - "whatsapp-square": 62476, - "wheelchair": 61843, - "whmcs": 62477, - "wifi": 61931, - "wikipedia-w": 62054, - "wind": 63278, - "window-close": 62480, - "window-maximize": 62160, - "window-minimize": 62161, - "window-restore": 62162, - "windows": 61818, - "wine-bottle": 63279, - "wine-glass": 62691, - "wine-glass-alt": 62926, - "wix": 62927, - "wizards-of-the-coast": 63280, - "wolf-pack-battalion": 62740, - "won-sign": 61785, - "wordpress": 61850, - "wordpress-simple": 62481, - "wpbeginner": 62103, - "wpexplorer": 62174, - "wpforms": 62104, - "wpressr": 62436, - "wrench": 61613, - "x-ray": 62615, - "xbox": 62482, - "xing": 61800, - "xing-square": 61801, - "y-combinator": 62011, - "yahoo": 61854, - "yandex": 62483, - "yandex-international": 62484, - "yarn": 63459, - "yelp": 61929, - "yen-sign": 61783, - "yin-yang": 63149, - "yoast": 62129, - "youtube": 61799, - "youtube-square": 62513, - "zhihu": 63039 -} \ No newline at end of file diff --git a/app/src/main/res/raw/node_modules_reactnativevectoricons_glyphmaps_fontawesome5free_meta.json b/app/src/main/res/raw/node_modules_reactnativevectoricons_glyphmaps_fontawesome5free_meta.json deleted file mode 100644 index 051deea6..00000000 --- a/app/src/main/res/raw/node_modules_reactnativevectoricons_glyphmaps_fontawesome5free_meta.json +++ /dev/null @@ -1,1511 +0,0 @@ -{ - "brands": [ - "500px", - "accessible-icon", - "accusoft", - "acquisitions-incorporated", - "adn", - "adobe", - "adversal", - "affiliatetheme", - "algolia", - "alipay", - "amazon-pay", - "amazon", - "amilia", - "android", - "angellist", - "angrycreative", - "angular", - "app-store-ios", - "app-store", - "apper", - "apple-pay", - "apple", - "artstation", - "asymmetrik", - "atlassian", - "audible", - "autoprefixer", - "avianex", - "aviato", - "aws", - "bandcamp", - "behance-square", - "behance", - "bimobject", - "bitbucket", - "bitcoin", - "bity", - "black-tie", - "blackberry", - "blogger-b", - "blogger", - "bluetooth-b", - "bluetooth", - "btc", - "buromobelexperte", - "buysellads", - "canadian-maple-leaf", - "cc-amazon-pay", - "cc-amex", - "cc-apple-pay", - "cc-diners-club", - "cc-discover", - "cc-jcb", - "cc-mastercard", - "cc-paypal", - "cc-stripe", - "cc-visa", - "centercode", - "centos", - "chrome", - "cloudscale", - "cloudsmith", - "cloudversify", - "codepen", - "codiepie", - "confluence", - "connectdevelop", - "contao", - "cpanel", - "creative-commons-by", - "creative-commons-nc-eu", - "creative-commons-nc-jp", - "creative-commons-nc", - "creative-commons-nd", - "creative-commons-pd-alt", - "creative-commons-pd", - "creative-commons-remix", - "creative-commons-sa", - "creative-commons-sampling-plus", - "creative-commons-sampling", - "creative-commons-share", - "creative-commons-zero", - "creative-commons", - "critical-role", - "css3-alt", - "css3", - "cuttlefish", - "d-and-d-beyond", - "d-and-d", - "dashcube", - "delicious", - "deploydog", - "deskpro", - "dev", - "deviantart", - "dhl", - "diaspora", - "digg", - "digital-ocean", - "discord", - "discourse", - "dochub", - "docker", - "draft2digital", - "dribbble-square", - "dribbble", - "dropbox", - "drupal", - "dyalog", - "earlybirds", - "ebay", - "edge", - "elementor", - "ello", - "ember", - "empire", - "envira", - "erlang", - "ethereum", - "etsy", - "expeditedssl", - "facebook-f", - "facebook-messenger", - "facebook-square", - "facebook", - "fantasy-flight-games", - "fedex", - "fedora", - "figma", - "firefox", - "first-order-alt", - "first-order", - "firstdraft", - "flickr", - "flipboard", - "fly", - "font-awesome-alt", - "font-awesome-flag", - "font-awesome-logo-full", - "font-awesome", - "fonticons-fi", - "fonticons", - "fort-awesome-alt", - "fort-awesome", - "forumbee", - "foursquare", - "free-code-camp", - "freebsd", - "fulcrum", - "galactic-republic", - "galactic-senate", - "get-pocket", - "gg-circle", - "gg", - "git-square", - "git", - "github-alt", - "github-square", - "github", - "gitkraken", - "gitlab", - "gitter", - "glide-g", - "glide", - "gofore", - "goodreads-g", - "goodreads", - "google-drive", - "google-play", - "google-plus-g", - "google-plus-square", - "google-plus", - "google-wallet", - "google", - "gratipay", - "grav", - "gripfire", - "grunt", - "gulp", - "hacker-news-square", - "hacker-news", - "hackerrank", - "hips", - "hire-a-helper", - "hooli", - "hornbill", - "hotjar", - "houzz", - "html5", - "hubspot", - "imdb", - "instagram", - "intercom", - "internet-explorer", - "invision", - "ioxhost", - "itunes-note", - "itunes", - "java", - "jedi-order", - "jenkins", - "jira", - "joget", - "joomla", - "js-square", - "js", - "jsfiddle", - "kaggle", - "keybase", - "keycdn", - "kickstarter-k", - "kickstarter", - "korvue", - "laravel", - "lastfm-square", - "lastfm", - "leanpub", - "less", - "line", - "linkedin-in", - "linkedin", - "linode", - "linux", - "lyft", - "magento", - "mailchimp", - "mandalorian", - "markdown", - "mastodon", - "maxcdn", - "medapps", - "medium-m", - "medium", - "medrt", - "meetup", - "megaport", - "mendeley", - "microsoft", - "mix", - "mixcloud", - "mizuni", - "modx", - "monero", - "napster", - "neos", - "nimblr", - "nintendo-switch", - "node-js", - "node", - "npm", - "ns8", - "nutritionix", - "odnoklassniki-square", - "odnoklassniki", - "old-republic", - "opencart", - "openid", - "opera", - "optin-monster", - "osi", - "page4", - "pagelines", - "palfed", - "patreon", - "paypal", - "penny-arcade", - "periscope", - "phabricator", - "phoenix-framework", - "phoenix-squadron", - "php", - "pied-piper-alt", - "pied-piper-hat", - "pied-piper-pp", - "pied-piper", - "pinterest-p", - "pinterest-square", - "pinterest", - "playstation", - "product-hunt", - "pushed", - "python", - "qq", - "quinscape", - "quora", - "r-project", - "raspberry-pi", - "ravelry", - "react", - "reacteurope", - "readme", - "rebel", - "red-river", - "reddit-alien", - "reddit-square", - "reddit", - "redhat", - "renren", - "replyd", - "researchgate", - "resolving", - "rev", - "rocketchat", - "rockrms", - "safari", - "sass", - "schlix", - "scribd", - "searchengin", - "sellcast", - "sellsy", - "servicestack", - "shirtsinbulk", - "shopware", - "simplybuilt", - "sistrix", - "sith", - "sketch", - "skyatlas", - "skype", - "slack-hash", - "slack", - "slideshare", - "snapchat-ghost", - "snapchat-square", - "snapchat", - "soundcloud", - "sourcetree", - "speakap", - "spotify", - "squarespace", - "stack-exchange", - "stack-overflow", - "staylinked", - "steam-square", - "steam-symbol", - "steam", - "sticker-mule", - "strava", - "stripe-s", - "stripe", - "studiovinari", - "stumbleupon-circle", - "stumbleupon", - "superpowers", - "supple", - "suse", - "teamspeak", - "telegram-plane", - "telegram", - "tencent-weibo", - "the-red-yeti", - "themeco", - "themeisle", - "think-peaks", - "trade-federation", - "trello", - "tripadvisor", - "tumblr-square", - "tumblr", - "twitch", - "twitter-square", - "twitter", - "typo3", - "uber", - "ubuntu", - "uikit", - "uniregistry", - "untappd", - "ups", - "usb", - "usps", - "ussunnah", - "vaadin", - "viacoin", - "viadeo-square", - "viadeo", - "viber", - "vimeo-square", - "vimeo-v", - "vimeo", - "vine", - "vk", - "vnv", - "vuejs", - "weebly", - "weibo", - "weixin", - "whatsapp-square", - "whatsapp", - "whmcs", - "wikipedia-w", - "windows", - "wix", - "wizards-of-the-coast", - "wolf-pack-battalion", - "wordpress-simple", - "wordpress", - "wpbeginner", - "wpexplorer", - "wpforms", - "wpressr", - "xbox", - "xing-square", - "xing", - "y-combinator", - "yahoo", - "yandex-international", - "yandex", - "yarn", - "yelp", - "yoast", - "youtube-square", - "youtube", - "zhihu" - ], - "regular": [ - "address-book", - "address-card", - "angry", - "arrow-alt-circle-down", - "arrow-alt-circle-left", - "arrow-alt-circle-right", - "arrow-alt-circle-up", - "bell-slash", - "bell", - "bookmark", - "building", - "calendar-alt", - "calendar-check", - "calendar-minus", - "calendar-plus", - "calendar-times", - "calendar", - "caret-square-down", - "caret-square-left", - "caret-square-right", - "caret-square-up", - "chart-bar", - "check-circle", - "check-square", - "circle", - "clipboard", - "clock", - "clone", - "closed-captioning", - "comment-alt", - "comment-dots", - "comment", - "comments", - "compass", - "copy", - "copyright", - "credit-card", - "dizzy", - "dot-circle", - "edit", - "envelope-open", - "envelope", - "eye-slash", - "eye", - "file-alt", - "file-archive", - "file-audio", - "file-code", - "file-excel", - "file-image", - "file-pdf", - "file-powerpoint", - "file-video", - "file-word", - "file", - "flag", - "flushed", - "folder-open", - "folder", - "font-awesome-logo-full", - "frown-open", - "frown", - "futbol", - "gem", - "grimace", - "grin-alt", - "grin-beam-sweat", - "grin-beam", - "grin-hearts", - "grin-squint-tears", - "grin-squint", - "grin-stars", - "grin-tears", - "grin-tongue-squint", - "grin-tongue-wink", - "grin-tongue", - "grin-wink", - "grin", - "hand-lizard", - "hand-paper", - "hand-peace", - "hand-point-down", - "hand-point-left", - "hand-point-right", - "hand-point-up", - "hand-pointer", - "hand-rock", - "hand-scissors", - "hand-spock", - "handshake", - "hdd", - "heart", - "hospital", - "hourglass", - "id-badge", - "id-card", - "image", - "images", - "keyboard", - "kiss-beam", - "kiss-wink-heart", - "kiss", - "laugh-beam", - "laugh-squint", - "laugh-wink", - "laugh", - "lemon", - "life-ring", - "lightbulb", - "list-alt", - "map", - "meh-blank", - "meh-rolling-eyes", - "meh", - "minus-square", - "money-bill-alt", - "moon", - "newspaper", - "object-group", - "object-ungroup", - "paper-plane", - "pause-circle", - "play-circle", - "plus-square", - "question-circle", - "registered", - "sad-cry", - "sad-tear", - "save", - "share-square", - "smile-beam", - "smile-wink", - "smile", - "snowflake", - "square", - "star-half", - "star", - "sticky-note", - "stop-circle", - "sun", - "surprise", - "thumbs-down", - "thumbs-up", - "times-circle", - "tired", - "trash-alt", - "user-circle", - "user", - "window-close", - "window-maximize", - "window-minimize", - "window-restore" - ], - "solid": [ - "ad", - "address-book", - "address-card", - "adjust", - "air-freshener", - "align-center", - "align-justify", - "align-left", - "align-right", - "allergies", - "ambulance", - "american-sign-language-interpreting", - "anchor", - "angle-double-down", - "angle-double-left", - "angle-double-right", - "angle-double-up", - "angle-down", - "angle-left", - "angle-right", - "angle-up", - "angry", - "ankh", - "apple-alt", - "archive", - "archway", - "arrow-alt-circle-down", - "arrow-alt-circle-left", - "arrow-alt-circle-right", - "arrow-alt-circle-up", - "arrow-circle-down", - "arrow-circle-left", - "arrow-circle-right", - "arrow-circle-up", - "arrow-down", - "arrow-left", - "arrow-right", - "arrow-up", - "arrows-alt-h", - "arrows-alt-v", - "arrows-alt", - "assistive-listening-systems", - "asterisk", - "at", - "atlas", - "atom", - "audio-description", - "award", - "baby-carriage", - "baby", - "backspace", - "backward", - "bacon", - "balance-scale", - "ban", - "band-aid", - "barcode", - "bars", - "baseball-ball", - "basketball-ball", - "bath", - "battery-empty", - "battery-full", - "battery-half", - "battery-quarter", - "battery-three-quarters", - "bed", - "beer", - "bell-slash", - "bell", - "bezier-curve", - "bible", - "bicycle", - "binoculars", - "biohazard", - "birthday-cake", - "blender-phone", - "blender", - "blind", - "blog", - "bold", - "bolt", - "bomb", - "bone", - "bong", - "book-dead", - "book-medical", - "book-open", - "book-reader", - "book", - "bookmark", - "bowling-ball", - "box-open", - "box", - "boxes", - "braille", - "brain", - "bread-slice", - "briefcase-medical", - "briefcase", - "broadcast-tower", - "broom", - "brush", - "bug", - "building", - "bullhorn", - "bullseye", - "burn", - "bus-alt", - "bus", - "business-time", - "calculator", - "calendar-alt", - "calendar-check", - "calendar-day", - "calendar-minus", - "calendar-plus", - "calendar-times", - "calendar-week", - "calendar", - "camera-retro", - "camera", - "campground", - "candy-cane", - "cannabis", - "capsules", - "car-alt", - "car-battery", - "car-crash", - "car-side", - "car", - "caret-down", - "caret-left", - "caret-right", - "caret-square-down", - "caret-square-left", - "caret-square-right", - "caret-square-up", - "caret-up", - "carrot", - "cart-arrow-down", - "cart-plus", - "cash-register", - "cat", - "certificate", - "chair", - "chalkboard-teacher", - "chalkboard", - "charging-station", - "chart-area", - "chart-bar", - "chart-line", - "chart-pie", - "check-circle", - "check-double", - "check-square", - "check", - "cheese", - "chess-bishop", - "chess-board", - "chess-king", - "chess-knight", - "chess-pawn", - "chess-queen", - "chess-rook", - "chess", - "chevron-circle-down", - "chevron-circle-left", - "chevron-circle-right", - "chevron-circle-up", - "chevron-down", - "chevron-left", - "chevron-right", - "chevron-up", - "child", - "church", - "circle-notch", - "circle", - "city", - "clinic-medical", - "clipboard-check", - "clipboard-list", - "clipboard", - "clock", - "clone", - "closed-captioning", - "cloud-download-alt", - "cloud-meatball", - "cloud-moon-rain", - "cloud-moon", - "cloud-rain", - "cloud-showers-heavy", - "cloud-sun-rain", - "cloud-sun", - "cloud-upload-alt", - "cloud", - "cocktail", - "code-branch", - "code", - "coffee", - "cog", - "cogs", - "coins", - "columns", - "comment-alt", - "comment-dollar", - "comment-dots", - "comment-medical", - "comment-slash", - "comment", - "comments-dollar", - "comments", - "compact-disc", - "compass", - "compress-arrows-alt", - "compress", - "concierge-bell", - "cookie-bite", - "cookie", - "copy", - "copyright", - "couch", - "credit-card", - "crop-alt", - "crop", - "cross", - "crosshairs", - "crow", - "crown", - "crutch", - "cube", - "cubes", - "cut", - "database", - "deaf", - "democrat", - "desktop", - "dharmachakra", - "diagnoses", - "dice-d20", - "dice-d6", - "dice-five", - "dice-four", - "dice-one", - "dice-six", - "dice-three", - "dice-two", - "dice", - "digital-tachograph", - "directions", - "divide", - "dizzy", - "dna", - "dog", - "dollar-sign", - "dolly-flatbed", - "dolly", - "donate", - "door-closed", - "door-open", - "dot-circle", - "dove", - "download", - "drafting-compass", - "dragon", - "draw-polygon", - "drum-steelpan", - "drum", - "drumstick-bite", - "dumbbell", - "dumpster-fire", - "dumpster", - "dungeon", - "edit", - "egg", - "eject", - "ellipsis-h", - "ellipsis-v", - "envelope-open-text", - "envelope-open", - "envelope-square", - "envelope", - "equals", - "eraser", - "ethernet", - "euro-sign", - "exchange-alt", - "exclamation-circle", - "exclamation-triangle", - "exclamation", - "expand-arrows-alt", - "expand", - "external-link-alt", - "external-link-square-alt", - "eye-dropper", - "eye-slash", - "eye", - "fast-backward", - "fast-forward", - "fax", - "feather-alt", - "feather", - "female", - "fighter-jet", - "file-alt", - "file-archive", - "file-audio", - "file-code", - "file-contract", - "file-csv", - "file-download", - "file-excel", - "file-export", - "file-image", - "file-import", - "file-invoice-dollar", - "file-invoice", - "file-medical-alt", - "file-medical", - "file-pdf", - "file-powerpoint", - "file-prescription", - "file-signature", - "file-upload", - "file-video", - "file-word", - "file", - "fill-drip", - "fill", - "film", - "filter", - "fingerprint", - "fire-alt", - "fire-extinguisher", - "fire", - "first-aid", - "fish", - "fist-raised", - "flag-checkered", - "flag-usa", - "flag", - "flask", - "flushed", - "folder-minus", - "folder-open", - "folder-plus", - "folder", - "font-awesome-logo-full", - "font", - "football-ball", - "forward", - "frog", - "frown-open", - "frown", - "funnel-dollar", - "futbol", - "gamepad", - "gas-pump", - "gavel", - "gem", - "genderless", - "ghost", - "gift", - "gifts", - "glass-cheers", - "glass-martini-alt", - "glass-martini", - "glass-whiskey", - "glasses", - "globe-africa", - "globe-americas", - "globe-asia", - "globe-europe", - "globe", - "golf-ball", - "gopuram", - "graduation-cap", - "greater-than-equal", - "greater-than", - "grimace", - "grin-alt", - "grin-beam-sweat", - "grin-beam", - "grin-hearts", - "grin-squint-tears", - "grin-squint", - "grin-stars", - "grin-tears", - "grin-tongue-squint", - "grin-tongue-wink", - "grin-tongue", - "grin-wink", - "grin", - "grip-horizontal", - "grip-lines-vertical", - "grip-lines", - "grip-vertical", - "guitar", - "h-square", - "hamburger", - "hammer", - "hamsa", - "hand-holding-heart", - "hand-holding-usd", - "hand-holding", - "hand-lizard", - "hand-middle-finger", - "hand-paper", - "hand-peace", - "hand-point-down", - "hand-point-left", - "hand-point-right", - "hand-point-up", - "hand-pointer", - "hand-rock", - "hand-scissors", - "hand-spock", - "hands-helping", - "hands", - "handshake", - "hanukiah", - "hard-hat", - "hashtag", - "hat-wizard", - "haykal", - "hdd", - "heading", - "headphones-alt", - "headphones", - "headset", - "heart-broken", - "heart", - "heartbeat", - "helicopter", - "highlighter", - "hiking", - "hippo", - "history", - "hockey-puck", - "holly-berry", - "home", - "horse-head", - "horse", - "hospital-alt", - "hospital-symbol", - "hospital", - "hot-tub", - "hotdog", - "hotel", - "hourglass-end", - "hourglass-half", - "hourglass-start", - "hourglass", - "house-damage", - "hryvnia", - "i-cursor", - "ice-cream", - "icicles", - "id-badge", - "id-card-alt", - "id-card", - "igloo", - "image", - "images", - "inbox", - "indent", - "industry", - "infinity", - "info-circle", - "info", - "italic", - "jedi", - "joint", - "journal-whills", - "kaaba", - "key", - "keyboard", - "khanda", - "kiss-beam", - "kiss-wink-heart", - "kiss", - "kiwi-bird", - "landmark", - "language", - "laptop-code", - "laptop-medical", - "laptop", - "laugh-beam", - "laugh-squint", - "laugh-wink", - "laugh", - "layer-group", - "leaf", - "lemon", - "less-than-equal", - "less-than", - "level-down-alt", - "level-up-alt", - "life-ring", - "lightbulb", - "link", - "lira-sign", - "list-alt", - "list-ol", - "list-ul", - "list", - "location-arrow", - "lock-open", - "lock", - "long-arrow-alt-down", - "long-arrow-alt-left", - "long-arrow-alt-right", - "long-arrow-alt-up", - "low-vision", - "luggage-cart", - "magic", - "magnet", - "mail-bulk", - "male", - "map-marked-alt", - "map-marked", - "map-marker-alt", - "map-marker", - "map-pin", - "map-signs", - "map", - "marker", - "mars-double", - "mars-stroke-h", - "mars-stroke-v", - "mars-stroke", - "mars", - "mask", - "medal", - "medkit", - "meh-blank", - "meh-rolling-eyes", - "meh", - "memory", - "menorah", - "mercury", - "meteor", - "microchip", - "microphone-alt-slash", - "microphone-alt", - "microphone-slash", - "microphone", - "microscope", - "minus-circle", - "minus-square", - "minus", - "mitten", - "mobile-alt", - "mobile", - "money-bill-alt", - "money-bill-wave-alt", - "money-bill-wave", - "money-bill", - "money-check-alt", - "money-check", - "monument", - "moon", - "mortar-pestle", - "mosque", - "motorcycle", - "mountain", - "mouse-pointer", - "mug-hot", - "music", - "network-wired", - "neuter", - "newspaper", - "not-equal", - "notes-medical", - "object-group", - "object-ungroup", - "oil-can", - "om", - "otter", - "outdent", - "pager", - "paint-brush", - "paint-roller", - "palette", - "pallet", - "paper-plane", - "paperclip", - "parachute-box", - "paragraph", - "parking", - "passport", - "pastafarianism", - "paste", - "pause-circle", - "pause", - "paw", - "peace", - "pen-alt", - "pen-fancy", - "pen-nib", - "pen-square", - "pen", - "pencil-alt", - "pencil-ruler", - "people-carry", - "pepper-hot", - "percent", - "percentage", - "person-booth", - "phone-slash", - "phone-square", - "phone-volume", - "phone", - "piggy-bank", - "pills", - "pizza-slice", - "place-of-worship", - "plane-arrival", - "plane-departure", - "plane", - "play-circle", - "play", - "plug", - "plus-circle", - "plus-square", - "plus", - "podcast", - "poll-h", - "poll", - "poo-storm", - "poo", - "poop", - "portrait", - "pound-sign", - "power-off", - "pray", - "praying-hands", - "prescription-bottle-alt", - "prescription-bottle", - "prescription", - "print", - "procedures", - "project-diagram", - "puzzle-piece", - "qrcode", - "question-circle", - "question", - "quidditch", - "quote-left", - "quote-right", - "quran", - "radiation-alt", - "radiation", - "rainbow", - "random", - "receipt", - "recycle", - "redo-alt", - "redo", - "registered", - "reply-all", - "reply", - "republican", - "restroom", - "retweet", - "ribbon", - "ring", - "road", - "robot", - "rocket", - "route", - "rss-square", - "rss", - "ruble-sign", - "ruler-combined", - "ruler-horizontal", - "ruler-vertical", - "ruler", - "running", - "rupee-sign", - "sad-cry", - "sad-tear", - "satellite-dish", - "satellite", - "save", - "school", - "screwdriver", - "scroll", - "sd-card", - "search-dollar", - "search-location", - "search-minus", - "search-plus", - "search", - "seedling", - "server", - "shapes", - "share-alt-square", - "share-alt", - "share-square", - "share", - "shekel-sign", - "shield-alt", - "ship", - "shipping-fast", - "shoe-prints", - "shopping-bag", - "shopping-basket", - "shopping-cart", - "shower", - "shuttle-van", - "sign-in-alt", - "sign-language", - "sign-out-alt", - "sign", - "signal", - "signature", - "sim-card", - "sitemap", - "skating", - "skiing-nordic", - "skiing", - "skull-crossbones", - "skull", - "slash", - "sleigh", - "sliders-h", - "smile-beam", - "smile-wink", - "smile", - "smog", - "smoking-ban", - "smoking", - "sms", - "snowboarding", - "snowflake", - "snowman", - "snowplow", - "socks", - "solar-panel", - "sort-alpha-down", - "sort-alpha-up", - "sort-amount-down", - "sort-amount-up", - "sort-down", - "sort-numeric-down", - "sort-numeric-up", - "sort-up", - "sort", - "spa", - "space-shuttle", - "spider", - "spinner", - "splotch", - "spray-can", - "square-full", - "square-root-alt", - "square", - "stamp", - "star-and-crescent", - "star-half-alt", - "star-half", - "star-of-david", - "star-of-life", - "star", - "step-backward", - "step-forward", - "stethoscope", - "sticky-note", - "stop-circle", - "stop", - "stopwatch", - "store-alt", - "store", - "stream", - "street-view", - "strikethrough", - "stroopwafel", - "subscript", - "subway", - "suitcase-rolling", - "suitcase", - "sun", - "superscript", - "surprise", - "swatchbook", - "swimmer", - "swimming-pool", - "synagogue", - "sync-alt", - "sync", - "syringe", - "table-tennis", - "table", - "tablet-alt", - "tablet", - "tablets", - "tachometer-alt", - "tag", - "tags", - "tape", - "tasks", - "taxi", - "teeth-open", - "teeth", - "temperature-high", - "temperature-low", - "tenge", - "terminal", - "text-height", - "text-width", - "th-large", - "th-list", - "th", - "theater-masks", - "thermometer-empty", - "thermometer-full", - "thermometer-half", - "thermometer-quarter", - "thermometer-three-quarters", - "thermometer", - "thumbs-down", - "thumbs-up", - "thumbtack", - "ticket-alt", - "times-circle", - "times", - "tint-slash", - "tint", - "tired", - "toggle-off", - "toggle-on", - "toilet-paper", - "toilet", - "toolbox", - "tools", - "tooth", - "torah", - "torii-gate", - "tractor", - "trademark", - "traffic-light", - "train", - "tram", - "transgender-alt", - "transgender", - "trash-alt", - "trash-restore-alt", - "trash-restore", - "trash", - "tree", - "trophy", - "truck-loading", - "truck-monster", - "truck-moving", - "truck-pickup", - "truck", - "tshirt", - "tty", - "tv", - "umbrella-beach", - "umbrella", - "underline", - "undo-alt", - "undo", - "universal-access", - "university", - "unlink", - "unlock-alt", - "unlock", - "upload", - "user-alt-slash", - "user-alt", - "user-astronaut", - "user-check", - "user-circle", - "user-clock", - "user-cog", - "user-edit", - "user-friends", - "user-graduate", - "user-injured", - "user-lock", - "user-md", - "user-minus", - "user-ninja", - "user-nurse", - "user-plus", - "user-secret", - "user-shield", - "user-slash", - "user-tag", - "user-tie", - "user-times", - "user", - "users-cog", - "users", - "utensil-spoon", - "utensils", - "vector-square", - "venus-double", - "venus-mars", - "venus", - "vial", - "vials", - "video-slash", - "video", - "vihara", - "volleyball-ball", - "volume-down", - "volume-mute", - "volume-off", - "volume-up", - "vote-yea", - "vr-cardboard", - "walking", - "wallet", - "warehouse", - "water", - "weight-hanging", - "weight", - "wheelchair", - "wifi", - "wind", - "window-close", - "window-maximize", - "window-minimize", - "window-restore", - "wine-bottle", - "wine-glass-alt", - "wine-glass", - "won-sign", - "wrench", - "x-ray", - "yen-sign", - "yin-yang" - ] -} diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml new file mode 100644 index 00000000..8f38c803 --- /dev/null +++ b/app/src/main/res/values-af/strings.xml @@ -0,0 +1,565 @@ + + LBRY + Maak navigasie-laai oop + Maak navigasielade toe + + + Soek content + U content + Beursie + Volgende + Redakteur se keuse + U etikette + Alle Content + Kanale + Biblioteek + Gepubliseer + Nuut Gepubliseer + Belonings + Uitnodigings + Instellings + Oor + Meld aan + Programstart het misluk. Kontroleer u dataverbinding en probeer weer. Stuur \'n e-pos na hello@lbry.com as hierdie probleem voortduur + Geen content om op die oomblik te vertoon nie. Herdefinieer u keuse of kyk later weer. + + + Welkom tot LBRY. + LBRY is \'n gemeenskapsgebaseerde contentsplatform waar je video\'s, musiek, boeke en meer kan vind en publiseer + Deur voort te gaan, stem ek in tot die <a href=\"https://lbry.com/termsofservice\">TDiensvoorwaardes</a> en bevestig dat ek ouer as 13 is. + Wag asseblief terwyl ons \'n paar dinge gereed maak... + Gebruik LBRY » + + + Soek films, musiek en meer + + + Vind Kanale om te volg + Kanale wat u volg + Vind + LBRY werk beter as u ten minste 5 skeppers volg waarvan u hou. Meld aan om skeppers wat u volg te wys as u reeds \'n account het. + Kies tot 5 skeppers om voort te gaan. + %1$doorblywende... + Gedoen + Almal + Ontdek nuwe kanale + + + Anoniem + + + Etikette + Deel + Publiseer + Fooi + Wysig + Verwyder + Aflaai + Oop + Rapporteer + Laai gedesentraliseerde data... + Verwante Content + Deel LBRY-inhoud + Beskou + Speel + Ongesteunde content + Jammer, ons kan nie die inhoud in die app vertoon nie. U kan die lêer %1$sin u aflaai-lêergids vind. + Daar is niks op hierdie plek nie. + Publiseer iets hier + Daar is tans geen toegang tot hierdie inhoud nie. Probeer asseblief weer later. + 0:00 + Die lêer by \"%1$s\" bestaan nie. + Bevestig die aankoop + Vee lêer uit + Is u seker dat u hierdie lêer van u toestel wil verwyder? + Kon nie laai nie %1$s. Probeer asseblief weer later. + Daar is tans geen rolverdeling beskikbaar nie. + Vee inhoud uit? + Is u seker dat u hierdie inhoud wil publiseer? Geen lêers sal van u toestel verwyder word nie. + Die inhoud is suksesvol uit die blockchain verwyder. + Die inhoud kon tans nie uitgevee word nie. Probeer asseblief weer later. + + %1$s uitsig + %1$s uitsig + + + Dit sal \"%1$s\" vir %2$s krediete koop + Dit sal \"%1$s\" vir %2$s krediete koop + + + + Daar is nog niks hier nie.\nProbeer asseblief later weer. + Content + Webwerf + gepubliseer + + %1$s volgeling + %1$s volgelinge + + + + Rekord + Neem \'n Foto + Laai \'n lêer op + Ons kon geen video\'s op u toestel vind nie. Neem \'n foto of neem \'n video op om aan die gang te kom. + Wag asseblief terwyl ons jou video\'s laai ... + LBRY benodig toegang om u video\'s, beelde en ander lêers vanaf u toestel te kan vertoon en publiseer. + LBRY benodig toegang tot u kamera om video\'s op te neem. + LBRY benodig toegang tot u kamera om foto\'s te neem. + Redigeer inhoud + Volwasse etikette + Prys + U content sal gratis wees. Druk op die skakelaar om \'n prys in te stel. + Contentadres + Dit enige + Adres + Die adres waar mense u content kan vind (bv. lbry://myvideo) + Lisensie + Lisensie beskrywing + Bykomende Opsies + Toon ekstra velde + Steek ekstra velde weg + Geen lêer gevind om te publiseer nie. + Videooptimalisering + \'N Kleinkiekie kan nie outomaties vanuit u inhoudslêer geskep word nie. + U video word geoptimaliseer vir beter ondersteuning op \'n wye verskeidenheid toestelle. U kan die oorblywende velde hieronder invul terwyl dit aan die gang is. + U video is suksesvol geoptimaliseer vir beter afspeel op soveel toestelle as moontlik. Gaan asseblief voort om u inhoud te publiseer. + U video kon nie geoptimaliseer word nie. Die lêer sal sonder veranderinge opgelaai word. + Voltooide Videoduur: %1$s + U kan nie op die oomblik inhoud publiseer nie, omdat die agtergronddiens steeds initialiseer. + U inhoud is suksesvol gepubliseer. Dit kan \'n paar oomblikke neem om op die blockchain te verskyn. + Videooptimalisering is aan die gang. Druk Annuleer onderaan die bladsy as u wil kanselleer. + Videooptimalisering is aan die gang. + Daar is geen kamera-app beskikbaar om video\'s op hierdie toestel op te neem nie. + Daar is geen kamera-app beskikbaar om foto\'s op hierdie toestel te neem nie. + + Verskaf \'n titel. + Spesifiseer \'n adres waar mense u inhoud kan vind. + U inhoudadres bevat ongeldige karakters. + U het reeds na die gespesifiseerde inhoudsadres gepubliseer. Voer \'n nuwe adres in. + Geen lêer gekies nie. Kies \'n video of neem \'n foto, of kies \'n lêer voordat u dit publiseer. + Voer \'n prys in of skakel die skakelaar af om u inhoud gratis te maak. + Kies \'n kleinkiekie om op te laai voordat u dit publiseer. + Wag tot die miniatuur klaar is met die oplaai voordat u dit publiseer. + + Taal + Engels + Sjinees + Frans + Duits + Japannees + Russiese + Spaans + Indonesiese + Italiaanse + Nederlands + Turks + Pools + Maleis + Portugees + Viëtnamees + Thai + Arabies + Tsjeggiese + Kroaties + Kambodja + Koreaanse + Noorse + Roemeens + Hindi + Griekse + + Geen + Publieke Domein + Kopiereg + Creative Commons Attribution 4.0 International + Creative Commons Attribution-ShareAlike 4.0 International + Creative Commons Attribution-NoDerivatives 4.0 International + Creative Commons Attribution-NonCommercial 4.0 International + Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International + Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International + + + LBC + USD + + + + Een of meer inhouditems kon tans nie uitgevee word nie. Probeer asseblief weer later. + + Is u seker dat u die geselekteerde inhouditems wil uitvee? + Is u seker dat u die geselekteerde inhouditems wil uitvee? + + + Die inhouditems is suksesvol uitgevee. + Die inhouditems is suksesvol uitgevee. + + + + Oeps! Iets het verkeerd geloop. + Laai installasie-ID. + Laai plaaslike bekende en volg merkers. + Laai LBC/USD wisselkoers. + Gebruiker geverifieer. + Installasie geregistreer. + Gelaaide intekeninge. + Ontbinde intekeninge. + + + Inhoud & Gebruikerskoppelvlak + Ander + Aktiveer donker tema + Wys volwasse inhoud + Wys URL-voorstelle + Kennisgewings + Abonnementen + Contentsbelangstellings + + Hou die LBRY-diens op die agtergrond vir verbeterde beursie- en netwerkprestasie + Neem deel aan die datanetwerk (benodig weer app en agtergronddiens) + + + %1$s - Soek + %1$s - Etikette + Soek vir \'%1$s\' + Verken content \'%1$s\' etikette + Kyk na inhoud by %1$s + Kyk na die %1$s kanaal + + + Geen resultate gevind vir \'%1$s\' nie. Voer \'n ander soekterm in. + U kan alles soek, insluitend films, musiek, e-boeke, sagteware en meer. + Geen verwante inhoud op die oomblik nie. + + + Balans + U het tans + U kan u krediete in USD omskakel en die omgeskakelde bedrag met \'n uitruil onttrek. <a href=\"https://lbry.com/faq/exchanges\">Learn more</a>. + <a href=\"https://bittrex.com/Account/Register?referralCode=4M1-P30-BON\">Skakel krediete na USD op Bittrex</a> + Jy het ook + Jy het vasgehou + in wenke + in u publikasies + in u steun + Verdien meer wenke deur cool video\'s op te laai + Die agtergronddiens initialiseer... + Die agtergronddiens word steeds geïnitialiseer. U kan die inhoud intussen verken en kyk. + U kan dit nie nou doen nie, want die agtergronddiens is nog besig om te initialiseer. + + \'N Rugsteun van u beursie word met lbry.tv gesinkroniseer + U beursie is tans nie met lbry.tv gesinkroniseer nie. U is verantwoordelik vir die rugsteun van u beursie. + <a href=\"https://lbry.com/faq/account-sync\">Wat beteken dit?</a> + <a href=\"https://lbry.com/faq/how-to-backup-wallet#android\">Wat beteken dit?</a></a> + + Krediete Ontvang + Gebruik hierdie beursie-adres om krediete te ontvang wat deur \'n ander gebruiker (of u self) gestuur is. + Kry nuwe adres + U kan enige tyd \'n nuwe adres genereer, en alle vorige adresse sal aanhou werk. Die gebruik van verskeie adresse kan nuttig wees om inkomende betalings uit verskeie bronne dop te hou. + + Stuur Krediete + Ontvanger adres + bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs + Bedrag + Stuur + + Onlangse Transaksies + Sien alles + Ontvang + Uitgereik + Publiseer + Ondersteuning + Verlaat + Kanaal + Kanaalopdatering + Publiseer Opdatering + + Wallet-sinkronisering + Sinkroniseer status + Op + Af + E-pos + Gekoppelde e-posadres + <a href=\"https://lbry.com/faq/how-to-backup-wallet#android\">Handmatige rugsteun</a> + <a href=\"https://lbry.com/faq/how-to-backup-wallet#sync\">Sinkroniseer FAQ</a> + 0 + LBC + + Account Aanbeveel + Met \'n lbry.tv-account kan je belonings verdien, je beursie rugsteun en alles sinchroniseer. + Sonder u account aanvaar u alle verantwoordelikheid om u beursie- en LBRY-data te beveilig. + Slaan Account Oor + Teken Aan + Adres gekopieër + + Voer \'n geldige adres in om krediete te stuur + Onvoldoende balans + Voer \'n geldige bedrag in + U krediete kon tans nie gestuur word nie. Probeer asseblief weer later. + Laai tans transaksies... + Daar is geen onlangse transaksies wat vertoon kan word nie. + Daar is tans geen transaksies wat vertoon kan word nie. + belasting %1$s + Transaksiegeskiedenis + + + U het %1$s krediete gestuur + U het %1$s krediete gestuur + + + + Pasmaak u etikette + Sorteer content volgens + Content van + Gewilde content + Nuwe content + Top content + Gewild + Top + Nuwe + Die afgelope 24 uur + Die afgelope week + Die afgelope maand + Verlede jaar + Van alle tye + van + vir + Filter vir + Almal + Etikette wat u volg + Aanpassen + Die geselekteerde aansig is nog nie beskikbaar nie. + Dit lyk asof u nog geen merkers gevolg het nie. + Soek vir meer etikette + U het nog geen merkers gevolg nie. Begin deur merkers by te voeg waarin u belangstel! + Ons kon nie nuwe merkers vind wat u nie volg nie. + Die \'%1$s\' tag is reeds bygevoeg. + U kan nie meer as 5 merkers byvoeg nie. + Stuur \'n wenk + Stuur \'n wenk aan %1$s + Dit sal verskyn as \'n wenk vir %1$s, wat die vermoë verhoog om ontdek te word terwyl dit aktief is. <a href=\"https://lbry.com/faq/tipping\">Learn more</a>. + Dit sal verskyn as \'n wenk vir %1$s, wat die vermoë verhoog om ontdek te word terwyl dit aktief is. <a href=\"https://lbry.com/faq/tipping\">Learn more</a>. + Kanselleer + Publiseer %1$s + Herpos u gunsteling inhoud om meer mense te help om dit te ontdek! + Kanaal om op te plaas + Wys gevorderd + Versteek gevorderd + Naam + 0.001 + Die inhoud is suksesvol weer gepos! + Die herposnaam bevat ongeldige karakters. + + U het %1$s krediete as \'n fooi gestuur, Mahalo! + U het %1$s krediete as \'n fooi gestuur, Mahalo! + + + + Verskaf \'n e-pos adres. + you@example.com + \'N E-pos is gestuur na + Klik op die skakel in die boodskap om aan te meld. + Herstuur + Gaan aan + Voer \'n geldige e-pos adres in + Volg die aanwysings in die e-pos wat na u adres gestuur word om voort te gaan. + U het suksesvol by lbry.tv aangemeld + Haal rekeninginligting op... + Pas beursie-data toe... + Voer die wagwoord in wat u gebruik het om u beursie te beveilig. + Voer \'n wagwoord in om u beursie te beveilig. + Opmerking: vir beursie-beveiligingsdoeleindes kan LBRY nie u wagwoord terugstel nie. + Wagwoord + Aktiveer sinkronisering + Die beursie-sinkronisering kon tans nie voltooi word nie. Probeer asseblief weer later. As hierdie probleem voortduur, stuur \'n e-pos na hello@lbry.com. + Telefoon Nommer + Voer u telefoonnommer in. + Stel nie belang nie + Handmatige Beloningverifikasie + Hierdie rekening moet hersien word voordat u aan die beloningsprogram kan deelneem. Dit kan vanaf \'n paar minute tot \'n paar dae duur. + As u aanhou om hierdie boodskap te sien, versoek u dat dit op die <a href=\"https://discordapp.com/invite/Z3bERWA\">LBRY Discord server</a>. + Geniet intussen gratis inhoud! + Verifieer Telefoonnommer + Voer die verifikasiekode in wat na %1$s gestuur is + 0000 + Verifieer + Voer \'n geldige telefoonnommer in. + Voer die verifikasiekode in wat na u telefoonnommer gestuur is. + + + U het nog geen merkers bygevoeg nie. Voeg etikette by om die ontdekking te verbeter. + Ons kon nie nuwe merkers vind wat nog nie bygevoeg is nie. + + + U het nie \'n kanaal geskep nie.\nBegin nou deur \'n nuwe kanaal te skep! + Skep \'n kanaal + Skep \'n kanaal... + Wysig kanaal + Vee seleksie uit? + Vee kanaal uit? + Is u seker dat u hierdie kanaal wil uitvee? + Die kanaal is suksesvol uitgevee. + Die kanaal kon tans nie uitgevee word nie. Probeer asseblief weer later. + Beskrywing + Ja + Nee + Toon opsionele velde + Steek opsionele velde weg + Save + Kanaalnaam + Titel + \@ + Deposito + Hierdie LBC bly joune. Dit is \'n deposito om die naam te bespreek en kan te eniger tyd ongedaan gemaak word. + LBRY benodig toegang om inhoud op u toestel af te laai. + LBRY benodig toegang om prente vanaf u toestelberging te laai. + Kies kleinkiekie + Kies voorblad + Die lêerpad kon nie vir die geselekteerde prent bepaal word nie. Kies \'n prent op \'n ander plek. + Wag tot die huidige oplaai voltooi is. + Die beeldoplaai-versoek het misluk. Probeer asseblief weer. + Oplaai... + Voer \'n kanaalnaam in. + Jou kanaalnaam bevat ongeldige karakters. + U het reeds \'n kanaal met dieselfde naam geskep. + Voer \'n geldige depositobedrag in. + Deposito kan nie hoër wees as u saldo nie. + Die kanaalstoorversoek het misluk. Probeer asseblief weer. + Die kanaal is suksesvol gestoor. + Die eis is hangende gepubliseer op die blockchain. U kan binne enkele oomblikke toegang tot die eis verkry of dit wysig. + Hangende + Skep + Een of meer kanale kon tans nie uitgevee word nie. Probeer asseblief weer later. + + \'N Minimum deposito van %1$s krediete word vereis. + \'N Minimum deposito van %1$s krediete word vereis. + + + Is u seker dat u die geselekteerde kanale wil uitvee? + Is u seker dat u die geselekteerde kanale wil uitvee? + + + Die kanale is suksesvol uitgevee. + Die kanale is suksesvol uitgevee. + + + + Met LBRY-krediete kan u inhoud publiseer of koop. + U kan gratis krediete ter waarde van $%1$s verwerf nadat u \'n e-posadres verstrek het. + <a href=\"https://lbry.com/faq/earn-credits\">Leer meer</a>. + Begin + abc123 + Eis + Voer \'n pasgemaakte beloningskode in om te eis. + Onopgeëiste + Pasgemaakte Kode + tot + Is u \'n supermodel of rockster wat \'n persoonlike beloningskode ontvang het? Eis dit hier op. + + U het %1$s krediete as beloning geëis. + U het %1$s krediete as beloning geëis. + + + %1$s beskikbare krediete + %1$s beskikbare krediete + + + + LBRY Uitnodigingsprogram + U kan ekstra krediete verdien vir elke persoon wat u uitnooi om LBRY te gebruik. + <a href=\"https://lbry.com/faq/invites\">Leer meer</a>. + Nooi Skakel Uit + Deel hierdie skakel met vriende (of vyande) en kry krediete wanneer hulle by lbry.tv aansluit. + U nooi skakel + Pasmaak nooi skakel + Nooi per e-pos uit + Nooi iemand wat u ken per e-pos en verdien krediete wanneer hulle by lbry.tv aansluit. + imaginary@friend.com + Uitnodiging + Uitnodigingsgeskiedenis + Verdien krediete vir die uitnodiging van \'n vriend, \'n vyand, \'n frenemy of \'n vriend. Almal het vryheid van inhoud nodig. + Beloning + Beweer + Geëis + Unclaimable + Nooi skakel gekopieër. + Uitnodiging gestuur na %1$s + + + Downloads + Aankope + Geskiedenis + U het geen inhoud op hierdie toestel afgelaai nie. + U het geen inhoud op hierdie toestel gesien nie. + U het geen inhoud op u rekening gekoop nie. + Verberg + Statistiek + Video + Audio + Beelde + MB + KB + GB + 0MB + + Is u seker dat u die geselekteerde lêers van u toestel wil verwyder? + Is u seker dat u die geselekteerde lêers van u toestel wil verwyder? + + + Die lêer is suksesvol uitgevee. + Die lêers is suksesvol uitgevee. + + + + Oor LBRY + Contentvryheid + LBRY is \'n gratis, oop en gemeenskapsbeheerde digitale mark. Dit is \'n gedesentraliseerde peer-to-peer-contentverspreidingsplatform vir skeppers om content op te laai en te deel, en verdien LBRY-krediete vir hul moeite. Gebruikers kan \'n wye verskeidenheid video\'s, musiek, e-boeke en ander digitale content waarin hulle belangstel vind. + Word Sosiaal + U kan interaksie met die LBRY-span en lede van die gemeenskap op Discord, Facebook, Instagram, Twitter of Reddit. + Programinligting + Laai... + <a href=\"https://lbry.com/faq/what-is-lbry\">Wat is LBRY?</a> + <a href=\"https://lbry.com/faq/android-basics\">Android Basics</a> + <a href=\"https://lbry.com/faq\">FAQ</a> + <a href=\"https://discordapp.com/invite/Z3bERWA\">Discord</a> + <a href=\"https://www.facebook.com/LBRYio\">Facebook</a> + <a href=\"https://www.instagram.com/LBRYio\">Instagram</a> + <a href=\"https://reddit.com/r/lbry\">Reddit</a> + <a href=\"https://t.me/lbryofficial\">Telegram</a> + <a href=\"https://twitter.com/LBRYio\">Twitter</a> + Dateer posvoorkeure op + Programweergawe + LBRY SDK + Platform + Installasie ID + Firebase Token + Logs + Stuur log + Gekoppelde e-posadres + Onbekend + Die lbrynet.log-lêer kon nie gevind word nie. + Die lbrynet.log-lêer kan nie gedeel word nie weens toestemmingsbeperkings. + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml new file mode 100644 index 00000000..72bfc7d8 --- /dev/null +++ b/app/src/main/res/values-es/strings.xml @@ -0,0 +1,578 @@ + + LBRY + Abrir menú de navegación + Cerrar menú de navegación + + + Explorar Contenido + Tu Contenido + Cartera + Siguiendo + Elección del Editor + Tus Etiquetas + Todo el Contenido + Canales + Biblioteca + Publicaciones + Nueva Publicación + Recompensas + Invitaciones + Ajustes + Acerca de + Registrarse +   +Error al iniciar la aplicación. Verifique su conexión de datos e inténtelo nuevamente. Si este problema persiste, por favor envíe un correo electrónico a hello@lbry.com + No hay contenido para mostrar en este momento. Por favor, refine su selección o realice la consulta más tarde. + + + Bienvenido a LBRY. + LBRY es una plataforma de contenido controlada por la comunidad donde puedes encontrar y publicar videos, música, libros y más. + Al continuar, acepto los <a href=\"https://lbry.com/termsofservice\"> Términos de servicio </a> y confirmo que tengo más de 13 años. + Por favor espere mientras preparamos algunas cosas... + Usa LBRY » + + + Busca vídeos, música y más + + + Busca Canales para seguir + Canales que sigues + Descubre + LBRY funciona mejor si sigues al menos a 5 creadores que te gustan. Inicie sesión para mostrar los creadores que sigue si ya tiene una cuenta. + Por favor selecciona hasta 5 creadores para continuar. + %1$drestante... + Hecho + Todo + Descubre nuevos canales + + + Anónimo + + + Etiquetas + Compartir + Republicar + Propina + Editar + Eliminar + Descargar + Abrir + Reportar + Cargando datos descentralizados... + Contenido Relacionado + Compartir contenido LBRY + Ver + Reproducir + Contenido no admitido + Lo sentimos, no podemos mostrar este contenido en la aplicación.Puedes encontrar el archivo%1$sen tu carpeta de descargas. +   +No hay nada en este lugar. + Publica algo aquí + No se puede acceder a este contenido en este momento. Por favor, inténtelo de nuevo más tarde. + 0:00 + El archivo en \"%1$s\" no existe. + Confirmar compra + Eliminar archivo + ¿Seguro que quieres eliminar este archivo de tu dispositivo? + Falló al cargar%1$s. Por favor, inténtelo de nuevo más tarde. + No hay sesión de reparto disponible en este momento. + ¿Eliminar contenido? +   +¿Estás seguro de que deseas publicar este contenido? No se eliminarán archivos de su dispositivo. +   +El contenido se eliminó con éxito de la cadena de bloques. +   +El contenido no se pudo eliminar en este momento. Por favor, inténtelo de nuevo más tarde. +  + + %1$svista + %1$s vistas + + + Esto comprará \"%1$s\" para %2$s crédito + Esto comprará \"%1$s\" para %2$s créditos + + + + Todavía no hay nada aquí. \ Por favor, vuelva más tarde. + Contenido + Página web + repostear + + %1$s seguidor + %1$s seguidores + + + +   +Grabar + Toma una foto + Cargar un archivo +   +No pudimos encontrar ningún video en su dispositivo. Tome una foto o grabe un video para comenzar. + Espera mientras cargamos tus videos ... + LBRY requiere acceso para poder mostrar y publicar sus videos, imágenes y otros archivos desde su dispositivo. + LBRY requiere acceso a su cámara para grabar videos. + LBRY requiere acceso a su cámara para tomar fotos. + Contenido editado + Etiquetas maduras + Precio + Tu contenido será gratis. Presiona el botón para establecer un precio. + Dirección de contenido + Aleatorizar + Dirección + La dirección donde las personas pueden encontrar su contenido (ex. lbry://myvideo) + Licencia + Descripción de la licencia + Opciones adicionales + Mostrar campos extras + Ocultar campos extras + No se ha encontrado ningún archivo para publicar. + Optimización de video + No se pudo crear una miniatura automáticamente desde su archivo de contenido. + Su video está siendo optimizado para un mejor soporte en una amplia gama de dispositivos. Puede completar los campos restantes a continuación mientras está en progreso. +   +Su video fue optimizado con éxito para una mejor reproducción en tantos dispositivos como sea posible. Por favor, proceda a publicar su contenido. +   +Tu video no pudo ser optimizado. El archivo se cargará sin cambios. + Duración completa del video: %1$s +   +No puede publicar contenido en este momento porque el servicio en segundo plano aún se está inicializando. +   +Su contenido fue publicado con éxito. Puede tardar unos minutos en aparecer en la cadena de bloques. + La optimización de video está en progreso. Si desea cancelar, presione Cancelar en la parte inferior de la página. + La optimización de video está en progreso. + No hay una aplicación de cámara disponible para grabar videos en este dispositivo. + No hay una aplicación de cámara disponible para tomar fotos en este dispositivo. + + Por favor proporcione un título. + Especifique una dirección donde las personas puedan encontrar su contenido. + Su dirección de contenido contiene caracteres no válidos. + Ya ha publicado en la dirección de contenido especificada. Por favor ingrese una nueva dirección. + Ningún archivo seleccionado. Por favor, elija un video, tome una foto, o seleccione un archivo antes de publicarlo. + Ingrese un precio o desactive la palanca para liberar su contenido. + Seleccione una miniatura para cargar antes de publicar. + Espere a que la miniatura finalice la carga antes de publicar. + + idioma + Inglés + Chino + Francés + Alemán + Japonés + Ruso + Español + Indonesio + Italiano + Holandés + Turco + Polaco + Malayo + Portugués + vietnamita + Tailandés + Arabe + Checo + Croata + Camboyano + Coreano + Noruego + Rumano + Hindi + Griego + + Ninguno + Dominio publico + Material con derechos de autor + Creative Commons Atribución 4.0 Internacional + Atribución-CompartirIgual 4.0 Internacional + Atribución-SinDerivadas 4.0 Internacional + Atribución-NoComercial 4.0 Internacional + Atribución-NoComercial-CompartirIgual 4.0 Internacional + Atribución-NoComercial-SinDerivadas 4.0 Internacional + + + LBC + USD + + + + Uno o más elementos de contenido no se pudieron eliminar en este momento. Por favor, inténtelo de nuevo más tarde. + + ¿Está seguro de que desea eliminar los elemento de contenido seleccionados? + ¿Está seguro de que desea eliminar los elementos de contenido seleccionados? + + + El elemento de contenido se eliminó correctamente. + Los elementos de contenido se eliminaron correctamente. + + + + ¡Uy! Algo salió mal. + ID de instalación cargada. + Etiquetas locales cargadas conocidas y seguidas. + Tipo de cambio LBC/USD cargado. + Usuario autenticado. + Instalación registrada. + Suscripciones cargadas. + Suscripciones resueltas. + + + Contenido & interfaz de usuario + Otro + Habilitar tema oscuro + Mostrar contenido para adultos + Mostrar sugerencias de URL + Notificaciones + Suscripciones + Intereses de contenido + + Mantenga el servicio LBRY ejecutándose en segundo plano para mejorar el rendimiento de la billetera y la red + Participar en la red de datos (requiere reiniciar la aplicación y el servicio en segundo plano) + + + %1$s- Buscar + %1$s- Etiqueta + Buscar \'%1$s\' + Explora la etiqueta \'%1$s\' + Ver contenido en%1$s + Ver el%1$sCanal + + + No se encontraron resultados para \'%1$s\'. Por favor, introduzca un término de búsqueda diferente. + Puede buscar cualquier cosa, incluyendo películas, música, libros electrónicos, software y más. + No hay contenido relacionado para mostrar en este momento. + + + Saldo + Tu Actualmente tienes + Puede convertir sus créditos a USD y retirar el monto convertido mediante un intercambio. <a href=\"https://lbry.com/faq/exchanges\">Learn more</a>. + <a href=\"https://bittrex.com/Account/Register?referralCode=4M1-P30-BON\">Convierta créditos a USD en Bittrex</a> + También tienes + En stake + en propinas + en tus publicaciones + en tus apoyos + Obtenga más propinas al subir videos geniales + El servicio en segundo plano se está inicializando ... + El servicio en segundo plano aún se está iniciando. Puede explorar y ver contenido mientras tanto. + No puede hacer esto ahora porque el servicio en segundo plano todavía no está iniciado. + + Una copia de seguridad de su billetera se sincronizara con lbry.tv + Su billetera no está sincronizada actualmente con lbry.tv. Usted es responsable de respaldar su billetera. + <a href=\"https://lbry.com/faq/account-sync\">¿Qué significa esto?</a> + <a href=\"https://lbry.com/faq/how-to-backup-wallet#android\">¿Qué significa esto?</a> + + Recibir créditos + Use esta dirección de billetera para recibir créditos enviados por otro usuario (o usted mismo). + Obtener nueva dirección + Puede generar una nueva dirección en cualquier momento, y cualquier dirección anterior continuará funcionando. El uso de varias direcciones puede ser útil para realizar un seguimiento de los pagos entrantes de múltiples fuentes. + + Enviar créditos + Dirección del destinatario + bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs + Monto + Enviar + + Transacciones recientes + Ver todo + Recibir + Gastar + Publicar + Apoyo + Abandonar + Canal + Actualización de canal + Publicar actualización + + sincronización de la billetera + estado de sincronización + Encendido + Apagado + Email + No hay correo electrónico conectado + <a href=\"https://lbry.com/faq/how-to-backup-wallet#android\">Copia de seguridad manual</a> + <a href=\"https://lbry.com/faq/how-to-backup-wallet#sync\">Preguntas frecuentes sobre sincronización</a> + 0 + LBC + + Cuenta recomendada + Una cuenta de lbry.tv te permite el ganar recompensas, realizar un respaldo de tu billetera y mantener todo sincronizado. + Sin una cuenta, tendrás que asumir la responsabilidad de asegurar tu billetera y datos de LBRY. + Omitir cuenta + Registrarse + Dirección copiada + + Ingrese una dirección válida para enviar créditos a + Saldo insuficiente + Por favor ingrese una cantidad válida + Sus créditos no se pudieron enviar en este momento. Por favor, inténtelo de nuevo más tarde. + Cargando transacciones... + No hay transacciones recientes para mostrar. + No hay transacciones para mostrar en este momento. + cuota %1$s + Historial de transacciones + + + Has enviado%1$s crédito + Has enviado %1$s créditos + + + + Personaliza tus etiquetas + Ordenar contenido por + Contenido de + Contenido destacado + Nuevo contenido + Mejor contenido + Destacado + Destacado + Nuevo + Últimas 24 horas + Semana pasada + Mes pasado + Año pasado + Todo el tiempo + para + por + Filtrar por + Todos + Etiquetas que sigues + Personalizar + La vista seleccionada aún no está disponible. + Parece que todavía no has seguido ninguna etiqueta. + Buscar mas etiquetas + Aún no has seguido ninguna etiqueta. ¡Comience agregando etiquetas que le interesen! + No pudimos encontrar las nuevas etiquetas que no estás siguiendo. + La etiqueta \'%1$s\' ya se ha agregado. + No puede agregar más de 5 etiquetas. + Envía una propina + Enviar una propina a %1$s + Esto aparecerá como una propina para %1$s,lo que aumentará su capacidad de ser descubierto mientras está activo. <a href=\"https://lbry.com/faq/tipping\">Aprender más</a>. + Esto aparecerá como una propina para %1$s, lo que aumentará la capacidad del canal para ser descubierto mientras está activo. <a href=\"https://lbry.com/faq/tipping\">Aprender más</a>. + Cancelar + Republicar %1$s + ¡Vuelva a publicar su contenido favorito para ayudar a que más personas lo descubran! + Canal para publicar en + Mostrar avanzado + Ocultar avanzado + Nombre + 0.001 + ¡El contenido se volvió a publicar correctamente! + El nombre de reenvío contiene caracteres no válidos. + + ¡has enviado %1$s LBC como propina, Mahalo! + ¡has enviado %1$s LBC como propina, Mahalo! + + + + Por favor introduce una dirección de email. + you@example.com + Un email ha sido enviado a + Por favor da clic en el link del mensaje para completar el inicio de sesión + Reenviar + Continuar + Por favor introduce una dirección valida de email + Por favor sigue las instrucciones en el email que te enviamos a tu dirección para continuar. + Has iniciado sesión con éxito en lbry.tv + Recuperando la información de tu cuenta... + Aplicando datos de billetera... + Por favor introduce la contraseña que utilizaste para asegurar tu billetera. + Por favor introduce una contraseña para asegurar tu billetera. + Nota: por cuestiones de seguridad de tu billetera LBRY no puede restablecer tu contraseña. + Contraseña + Habilitar la sincronización + La operación de sincronización de billetera no se pudo completar en este momento. Por favor, inténtelo de nuevo más tarde. Si este problema persiste, envíe un correo electrónico a hello@lbry.com. + Número de teléfono + Por favor, introduzca su número de teléfono. + No estoy interesado + Verificación manual de recompensas + Esta cuenta debe someterse a revisión antes de poder participar en el programa de recompensas. Esto puede llevar desde varios minutos hasta varios días. + Si continúa viendo este mensaje, solicite ser verificado en el <a href=\"https://discordapp.com/invite/Z3bERWA\">LBRY Discord server</a>. + ¡Mientras tanto, disfruta de contenido gratuito! + Verificar número de teléfono + Ingrese el código de verificación enviado a%1$s + 0000 + Verificar + Por favor ingrese un número de teléfono válido. + Ingrese el código de verificación enviado a su número de teléfono. + + + Aún no ha agregado ninguna etiqueta. Agregue etiquetas para mejorar el alcance. + No pudimos encontrar nuevas etiquetas que aún no se hayan agregado. + + + No has creado ningún canal +¡Empieza ahora creando un nuevo canal! + Crear un canal + Crear un canal... + Editar canal + ¿Eliminar selección? + ¿Eliminar canal? + ¿Estás seguro de que deseas eliminar este canal? + El canal se eliminó correctamente. + El canal no se pudo eliminar en este momento. Por favor, inténtelo de nuevo más tarde. + Descripción + + No + Mostrar campos adicionales + Ocultar campos opcionales + Guardar + Nombre del canal + Titulo + \@ + Depositar + Estos LBC siguen siendo tuyos, Este es un deposito para reservar el nombre y puede ser deshecho en cualquier momento. + LBRY requiere acceso para descargar contenido a su dispositivo. + LBRY requiere acceso para cargar imágenes desde el almacenamiento de su dispositivo. + Seleccionar miniatura + Seleccionar imagen de portada + No se pudo determinar la ruta del archivo para la imagen seleccionada. Seleccione una imagen en una ubicación diferente. + Espera a que finalice la carga actual. + La solicitud de carga de la imagen ha fallado. Inténtalo de nuevo. + Subiendo... + Por favor ingrese un nombre de canal. + El nombre de tu canal contiene caracteres no válidos. + Ya has creado un canal con el mismo nombre. + Por favor ingrese un monto de depósito válido. + El deposito no puede ser mas alto que tu balance. + La solicitud de guardar el canal falló. Inténtalo de nuevo. + El canal se guardó correctamente. + El reclamo está pendiente de publicación en blockchain. Podrá acceder o editar el reclamo en unos momentos. + Pendiente + Crear + Uno o más canales no se pudieron eliminar en este momento. Por favor, inténtelo de nuevo más tarde. + + Se requiere un depósito mínimo de %1$s crédito. + Se requiere un depósito mínimo de %1$s créditos. + + + ¿Está seguro de que desea eliminar el canal seleccionado? + ¿Está seguro de que desea eliminar los canales seleccionados? + + + El canal se eliminó correctamente. + Los canales fueron eliminados con éxito. + + + + Los créditos LBRY le permiten publicar o comprar contenido. + Puede obtener créditos gratuitos por valor de $ %1$s después de proporcionar una dirección de correo electrónico. + <a href=\"https://lbry.com/faq/earn-credits\">Aprende más</a>. + Empezar + abc123 + Reclamación + Ingrese un código de recompensa personalizado para reclamar. + No reclamado + Código personalizado + hasta + ¿Eres una supermodelo o estrella de rock que recibió un código de recompensa personalizado? Reclámalo aquí. + + Has reclamado %1$s crédito como recompensa. + Has reclamado %1$s créditos como recompensa. + + + %1$s créditos disponibles + %1$s créditos disponibles + + + + Programa de Invitación LBRY + Puede obtener créditos adicionales por cada persona que invite a usar LBRY. + <a href=\"https://lbry.com/faq/invites\">Aprende más</a>. + Enlace de invitación + Comparta este enlace con amigos (o enemigos) y obtenga créditos cuando se unan a lbry.tv. + Tu enlace de invitación + Personalizar enlace de invitación + Invitar por Email + Invita a alguien que conozcas por correo electrónico y gana créditos cuando se una a lbry.tv. + amigo@imaginario.es + Invitar + Historial de Invitaciones + Gana créditos por invitar a un amigo, un enemigo, un amigo-enemigo o un enemigo-amigo. Todos necesitan libertad de contenido. + Recompensa + Reclamado + Reclamable + No Reclamable + Enlace de invitación copiado. + Invitación enviada a %1$s + + + Descargas + Compras + Historial + No tienes ningún contenido descargado en este dispositivo. + No has visto ningún contenido en este dispositivo. + No ha comprado ningún contenido en su cuenta. + Mantener + Estadísticas + Vídeo + Audio + Imágenes + MB + KB + GB + 0MB + + ¿Está seguro de que desea eliminar el archivo seleccionado de su dispositivo? + ¿Está seguro de que desea eliminar los archivos seleccionados de su dispositivo? + + + El archivo fue con éxito suprimido. + Los archivos fueron con éxito suprimidos. + + + + Sobre LBRY + Libertad de contenido + LBRY es un mercado digital gratuito, abierto y gestionado por la comunidad. Es una plataforma descentralizada de distribución de contenido entre pares para que los creadores carguen y compartan contenido y obtengan créditos LBRY por su esfuerzo. Los usuarios podrán encontrar una amplia selección de videos, música, libros electrónicos y otro contenido digital que les interese. + Sé social + Puede interactuar con el equipo LBRY y los miembros de la comunidad en Discord, Facebook, Instagram, Twitter o Reddit. + Info de la App + Cargando... + <a href=\"https://lbry.com/faq/what-is-lbry\">¿Qué es LBRY?</a> + <a href=\"https://lbry.com/faq/android-basics\">Android Básicos</a> + <a href=\"https://lbry.com/faq\">FAQ</a> + <a href=\"https://discordapp.com/invite/Z3bERWA\">Discord</a> + <a href=\"https://www.facebook.com/LBRYio\">Facebook</a> + <a href=\"https://www.instagram.com/LBRYio\">Instagram</a> + <a href=\"https://reddit.com/r/lbry\">Reddit</a> + <a href=\"https://t.me/lbryofficial\">Telegram</a> + <a href=\"https://twitter.com/LBRYio\">Twitter</a> + Actualizar preferencias de correo + Versión de la app + LBRY SDK + Plataforma + ID de instalación + Token de Firebase + Registros + Enviar registros + Correo electrónico conectado + Desconocido + No se pudo encontrar el archivo lbrynet.log. + El archivo lbrynet.log no se puede compartir debido a restricciones de permisos. + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 00000000..59546685 --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,61 @@ + + + #40B887 + #2F9176 + #38D9A9 + + #101010 + #000000 + #000000 + #CCCCCC + #000000 + #AAAAAA + #0E0E0E + #CC000000 + #55000000 + #F4E866 + + #33000000 + @color/nextLbryGreen + @color/nextLbryGreen + @color/nextLbryGreenSemiTransparent + + + #EEEEEE + #757575 + #999999 + #333333 + #5F5F5F + #333333 + + #40B887 + #2F9176 + #38D9A9 + #3338D9A9 + #329A7E + #77D510B8 + + #292929 + #0A0A0A + + #CCCCCC + #AAAAAA + #E5E5E5 + #F1F1F1 + #FFBB00 + #FF0000 + #E35454 + #FFFFFF + #3971DB + #2196f3 + + @color/nextLbryGreen + #F6A637 + #FF4A7D + #26BCF7 + + #252525 + + #CAEDB9 + #CC333333 + \ No newline at end of file diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml new file mode 100644 index 00000000..cd72b05c --- /dev/null +++ b/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1c703f9e..d35fef96 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,12 +1,61 @@ - #40B89A - #303F9F - #FFFFFF + #40B887 + #2F9176 + #38D9A9 - #FF0000 - #00C000 - #FFFFFF + #EFEFEF + #FFFFFF + #FFFFFF + #333333 + #FFFFFF + #444444 + #F1F1F1 + #CC000000 + #55000000 + #F4E866 + + #333333 + #33000000 + @color/nextLbryGreen + @color/nextLbryGreen + @color/nextLbryGreenSemiTransparent + + #222222 + #AAAAAA + #333333 + #333333 + #777777 + #F1F1F1 + + #40B887 #2F9176 #38D9A9 + #3338D9A9 + #E3F6F1 + #77F255DA + + #DFDFDF + #F1F1F1 + + #CCCCCC + #AAAAAA + #E5E5E5 + #F1F1F1 + #FFBB00 + #FF0000 + #E35454 + #FFFFFF + #3971DB + #2196f3 + + @color/nextLbryGreen + #F6A637 + #FF4A7D + #26BCF7 + + #D5D5D5 + + #CAEDB9 + #CC333333 \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..4ab4520f --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,8 @@ + + + 16dp + 16dp + 8dp + 176dp + 16dp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9ceff696..141c0d66 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,14 +1,565 @@ - LBRY - io.lbry.browser.LBRY_ENGAGEMENT_CHANNEL + Open navigation drawer + Close navigation drawer - Running - Service Status - Stopped - START - STOP + + Find Content + Your Content + Wallet + Following + Editor\'s Choice + Your Tags + All Content + Channels + Library + Publishes + New Publish + Rewards + Invites + Settings + About + Sign In + App startup failed. Please check your data connection and try again. If this problem persists, please email hello@lbry.com + No content to display at this time. Please refine your selection or check back later. - Unit tests - RUN + + Welcome to LBRY. + LBRY is a community-controlled content platform where you can find and publish videos, music, books, and more. + By continuing, I agree to the <a href="https://lbry.com/termsofservice">Terms of Service</a> and confirm I am over the age of 13. + Please wait while we get some things ready... + Use LBRY » + + + Search videos, music and more + + + Find Channels to follow + Channels you follow + Discover + LBRY works better if you follow at least 5 creators you like. Sign in to show creators you follow if you already have an account. + Please select up to 5 creators to continue. + %1$d remaining... + Done + All + Discover new channels + + + Anonymous + + + Tags + Share + Repost + Tip + Edit + Delete + Download + Open + Report + Loading decentralized data... + Related Content + Share LBRY content + View + Play + Unsupported Content + Sorry, we are unable to display this content in the app. You can find the file %1$sin your downloads folder. + There\'s nothing at this location. + Publish something here + This content cannot be accessed at this time. Please try again later. + 0:00 + The file at "%1$s" does not exist. + Confirm Purchase + Delete file + Are you sure you want to remove this file from your device? + Failed to load %1$s. Please try again later. + There is no cast session available at this time. + Delete content? + Are you sure you want to unpublish this content? No files will be removed from your device. + The content was successfully deleted from the blockchain. + The content could not be deleted at this time. Please try again later. + + %1$s view + %1$s views + + + This will purchase "%1$s" for %2$s credit + This will purchase "%1$s" for %2$s credits + + + + There\'s nothing here yet.\nPlease check back later. + Content + Website + reposted + + %1$s follower + %1$s followers + + + + Record + Take a Photo + Upload a file + We could not find any videos on your device. Take a photo or record a video to get started. + Please wait while we load your videos... + LBRY requires access to be able to display and publish your videos, images and other files from your device. + LBRY requires access to your camera to record videos. + LBRY requires access to your camera to take photos. + Edit content + Mature tags + Price + Your content will be free. Press the toggle to set a price. + Content address + Randomize + Address + The address where people can find your content (ex. lbry://myvideo) + License + License description + Additional Options + Show extra fields + Hide extra fields + No file found to publish. + Video optimization + A thumbnail could not be automatically created from your content file. + Your video is being optimized for better support on a wide range of devices. You can fill out the remaining fields below while this is in progress. + Your video was successfully optimized for better playback across as many devices as possible. Please proceed to publish your content. + Your video could not be optimized. The file will be uploaded with no changes. + Completed Video Duration: %1$s + You cannot publish content right now because the background service is still initializing. + Your content was successfully published. It may take a few moments to appear on the blockchain. + Video optimization is in progress. If you wish to cancel, press Cancel at the bottom of the page. + Video optimization is in progress. + There is no camera app available to record videos on this device. + There is no camera app available to take photos on this device. + + Please provide a title. + Please specify an address where people can find your content. + Your content address contains invalid characters. + You have already published to the specified content address. Please enter a new address. + No file selected. Please choose a video or take a photo, or select a file before publishing. + Please enter a price or turn off the toggle to make your content free. + Please select a thumbnail to upload before publishing. + Please wait for the thumbnail to finish uploading before publishing. + + Language + English + Chinese + French + German + Japanese + Russian + Spanish + Indonesian + Italian + Dutch + Turkish + Polish + Malay + Portuguese + Vietnamese + Thai + Arabic + Czech + Croatian + Cambodian + Korean + Norwegian + Romanian + Hindi + Greek + + None + Public Domain + Copyrighted + Creative Commons Attribution 4.0 International + Creative Commons Attribution-ShareAlike 4.0 International + Creative Commons Attribution-NoDerivatives 4.0 International + Creative Commons Attribution-NonCommercial 4.0 International + Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International + Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International + + + LBC + USD + + + + One or more content items could not be deleted at this time. Please try again later. + + Are you sure you want to delete the selected content item? + Are you sure you want to delete the selected content items? + + + The content item was successfully deleted. + The content items were successfully deleted. + + + + Oops! Something went wrong. + Loaded Installation ID. + Loaded local known and followed tags. + Loaded LBC/USD exchange rate. + User authenticated. + Installation registered. + Loaded subscriptions. + Resolved subscriptions. + + + Content & User interface + Other + Enable dark theme + Show mature content + Show URL suggestions + Notifications + Subscriptions + Content Interests + + Keep the LBRY service running in the background for improved wallet and network performance + Participate in the data network (requires app and background service restart) + + + %1$s - Search + %1$s - Tag + Search for \'%1$s\' + Explore the \'%1$s\' tag + View content at %1$s + View the %1$s channel + + + No results found for \'%1$s\'. Please enter a different search term. + You can search for anything including movies, music, ebooks, software and more. + No related content to display at this time. + + + Balance + You currently have + You can convert your credits to USD and withdraw the converted amount using an exchange. <a href="https://lbry.com/faq/exchanges">Learn more</a>. + <a href="https://bittrex.com/Account/Register?referralCode=4M1-P30-BON">Convert credits to USD on Bittrex</a> + You also have + You staked + in tips + in your publishes + in your supports + Earn more tips by uploading cool videos + The background service is initializing... + The background service is still initializing. You can explore and watch content in the mean time. + You cannot do this right now because the background service is still initializing. + + A backup of your wallet is synced with lbry.tv + Your wallet is not currently synced with lbry.tv. You are responsible for backing up your wallet. + <a href="https://lbry.com/faq/account-sync">What does this mean?</a> + <a href="https://lbry.com/faq/how-to-backup-wallet#android">What does this mean?</a> + + Receive Credits + Use this wallet address to receive credits sent by another user (or yourself). + Get new address + You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources. + + Send Credits + Recipient address + bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs + Amount + Send + + Recent Transactions + View All + Receive + Spend + Publish + Support + Abandon + Channel + Channel Update + Publish Update + + Wallet Sync + Sync status + On + Off + Email + No connected email + <a href="https://lbry.com/faq/how-to-backup-wallet#android">Manual backup</a> + <a href="https://lbry.com/faq/how-to-backup-wallet#sync">Sync FAQ</a> + 0 + LBC + + Account Recommended + A lbry.tv account allows you to earn rewards, backup your wallet, and keep everything in sync. + Without an account, you assume all responsibility for securing your wallet and LBRY data. + Skip Account + Sign up + Address copied + + Please enter a valid address to send credits to + Insufficient balance + Please enter a valid amount + Your credits could not be sent at this time. Please try again later. + Loading transactions... + There are no recent transactions to display. + There are no transactions to display at this time. + fee %1$s + Transaction History + + + You sent %1$s credit + You sent %1$s credits + + + + Customize your tags + Sort content by + Content from + Trending content + New content + Top content + Trending + Top + New + Past 24 hours + Past week + Past month + Past year + All time + from + for + Filter for + Everyone + Tags you follow + Customize + The selected view is not yet available. + It looks like you have not followed any tags yet. + Search for more tags + You have not followed any tags yet. Get started by adding tags that you are interested in! + We could not find new tags that you\'re not following. + The \'%1$s\' tag has already been added. + You cannot add more than 5 tags. + Send a tip + Send a tip to %1$s + This will appear as a tip for %1$s, which will boost its ability to be discovered while active. <a href="https://lbry.com/faq/tipping">Learn more</a>. + This will appear as a tip for %1$s, which will boost the channel\'s ability to be discovered while active. <a href="https://lbry.com/faq/tipping">Learn more</a>. + Cancel + Repost %1$s + Repost your favorite content to help more people discover them! + Channel to post on + Show advanced + Hide advanced + Name + 0.001 + The content was successfully reposted! + The repost name contains invalid characters. + + You sent %1$s credit as a tip, Mahalo! + You sent %1$s credits as a tip, Mahalo! + + + + Please provide an email address. + you@example.com + An email has been sent to + Please click the link in the message to complete signing in. + Resend + Continue + Please enter a valid email address + Please follow the instructions in the email sent to your address to continue. + You have successfully signed in to lbry.tv + Retrieving account information... + Applying wallet data... + Please enter the password you used to secure your wallet. + Please enter a password to secure your wallet. + Note: for wallet security purposes, LBRY is unable to reset your password. + Password + Enable sync + The wallet sync operation could not be completed at this time. Please try again later. If this problem persists, please send an email to hello@lbry.com. + Phone Number + Please enter your phone number. + Not interested + Manual Reward Verification + This account must undergo review before you can participate in the rewards program. This can take anywhere from several minutes to several days. + If you continue to see this message, please request to be verified on the <a href="https://discordapp.com/invite/Z3bERWA">LBRY Discord server</a>. + Please enjoy free content in the meantime! + Verify Phone Number + Please enter the verification code sent to %1$s + 0000 + Verify + Please enter a valid phone number. + Please enter the verification code sent to your phone number. + + + You have not added any tags yet. Add tags to improve discovery. + We could not find new tags that have not been added yet. + + + You have not created a channel.\nStart now by creating a new channel! + Create a channel + Create a channel... + Edit channel + Delete selection? + Delete channel? + Are you sure you want to delete this channel? + The channel was successfully deleted. + The channel could not be deleted at this time. Please try again later. + Description + Yes + No + Show optional fields + Hide optional fields + Save + Channel name + Title + \@ + Deposit + This LBC remains yours. It is a deposit to reserve the name and can be undone at any time. + LBRY requires access to download content to your device. + LBRY requires access to load images from your device storage. + Select thumbnail + Select cover image + The file path could not be determined for the selected image. Please select an image in a different location. + Please wait for the current upload to finish. + The image upload request failed. Please try again. + Uploading... + Please enter a channel name. + Your channel name contains invalid characters. + You have already created a channel with the same name. + Please enter a valid deposit amount. + Deposit cannot be higher than your balance. + The channel save request failed. Please try again. + The channel was successfully saved. + The claim is pending publish on the blockchain. You will be able to access or edit the claim in a few moments. + Pending + Create + One or more channels could not be deleted at this time. Please try again later. + + A minimum deposit of %1$s credit is required. + A minimum deposit of %1$s credits is required. + + + Are you sure you want to delete the selected channel? + Are you sure you want to delete the selected channels? + + + The channel was successfully deleted. + The channels were successfully deleted. + + + + LBRY credits allow you to publish or purchase content. + You can obtain free credits worth $%1$s after you provide an email address. + <a href="https://lbry.com/faq/earn-credits">Learn more</a>. + Get started + abc123 + Claim + Please enter a custom reward code to claim. + Unclaimed + Custom Code + up to + Are you a supermodel or rockstar that received a custom reward code? Claim it here. + + You have claimed %1$s credit as a reward. + You have claimed %1$s credits as a reward. + + + %1$s available credit + %1$s available credits + + + + LBRY Invite Program + You can earn extra credits for each person you invite to use LBRY. + <a href="https://lbry.com/faq/invites">Learn more</a>. + Invite Link + Share this link with friends (or enemies) and get credits when they join lbry.tv. + Your invite link + Customize invite link + Invite by Email + Invite someone you know by email and earn credits when they join lbry.tv. + imaginary@friend.com + Invite + Invite History + Earn credits for invite a friend, an enemy, a frenemy, or an enefriend. Everyone needs content freedom. + Reward + Claimed + Claimable + Unclaimable + Invite link copied. + Invite sent to %1$s + + + Downloads + Purchases + History + You have not downloaded any content to this device. + You have not viewed any content on this device. + You have not purchased any content on your account. + Hide + Stats + Video + Audio + Images + MB + KB + GB + 0MB + + Are you sure you want to remove the selected file from your device? + Are you sure you want to remove the selected files from your device? + + + The file was successfully deleted. + The files were successfully deleted. + + + + About LBRY + Content Freedom + LBRY is a free, open, and community-run digital marketplace. It is a decentralized peer-to-peer content distribution platform for creators to upload and share content, and earn LBRY credits for their effort. Users will be able to find a wide selection of videos, music, ebooks and other digital content they are interested in. + Get Social + You can interact with the LBRY team and members of the community on Discord, Facebook, Instagram, Twitter or Reddit. + App info + Loading... + <a href="https://lbry.com/faq/what-is-lbry">What is LBRY?</a> + <a href="https://lbry.com/faq/android-basics">Android Basics</a> + <a href="https://lbry.com/faq">FAQ</a> + <a href="https://discordapp.com/invite/Z3bERWA">Discord</a> + <a href="https://www.facebook.com/LBRYio">Facebook</a> + <a href="https://www.instagram.com/LBRYio">Instagram</a> + <a href="https://reddit.com/r/lbry">Reddit</a> + <a href="https://t.me/lbryofficial">Telegram</a> + <a href="https://twitter.com/LBRYio">Twitter</a> + Update mailing preferences + App version + LBRY SDK + Platform + Installation ID + Firebase Token + Logs + Send log + Connected email + Unknown + The lbrynet.log file could not be found. + The lbrynet.log file cannot be shared due to permission restrictions. + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..7c5e55a2 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml deleted file mode 100644 index 12896d72..00000000 --- a/app/src/main/res/values/themes.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/xml/filepaths.xml b/app/src/main/res/xml/filepaths.xml index 6c65d1c7..72bd94c2 100644 --- a/app/src/main/res/xml/filepaths.xml +++ b/app/src/main/res/xml/filepaths.xml @@ -1,3 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml new file mode 100644 index 00000000..9999c440 --- /dev/null +++ b/app/src/main/res/xml/settings.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/test/java/io/lbry/browser/ExampleUnitTest.java b/app/src/test/java/io/lbry/browser/ExampleUnitTest.java new file mode 100644 index 00000000..8374b9c4 --- /dev/null +++ b/app/src/test/java/io/lbry/browser/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package io.lbry.browser; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index a38cb3cc..e14841db 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,16 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext { - buildToolsVersion = "28.0.3" - minSdkVersion = 21 - compileSdkVersion = 28 - targetSdkVersion = 28 - } + repositories { google() jcenter() + } dependencies { - classpath 'com.android.tools.build:gradle:3.6.0' + classpath 'com.android.tools.build:gradle:3.6.3' classpath 'com.google.gms:google-services:4.2.0' + // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -23,18 +20,10 @@ allprojects { repositories { google() jcenter() - maven { url 'https://jitpack.io' } - mavenLocal() - maven { - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - url("$rootDir/../node_modules/react-native/android") - } - maven { - // Android JSC is installed from npm - url("$rootDir/../node_modules/jsc-android/dist") - } - flatDir { - dirs 'libs' - } + maven { url "https://jitpack.io" } } } + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties index 03440afb..199d16ed 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,23 +1,20 @@ # Project-wide Gradle settings. - # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. - # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx10248m -XX:MaxPermSize=256m -org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 - +org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -org.gradle.parallel=true - -org.gradle.configureondemand=true - +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX android.enableJetifier=true + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf0..f6b961fd 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9a201d9e..d3bb33d6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Wed Feb 26 16:43:39 WAT 2020 +#Fri Apr 03 15:21:09 WAT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b0d6d0ab..cccdd3d5 100644 --- a/gradlew +++ b/gradlew @@ -1,21 +1,5 @@ #!/usr/bin/env sh -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - ############################################################################## ## ## Gradle start up script for UN*X @@ -44,7 +28,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/gradlew.bat b/gradlew.bat index 15e1ee37..e95643d6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,19 +1,3 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem http://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -30,7 +14,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" +set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/settings.gradle b/settings.gradle index 79f9cb53..7d91e8cc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,24 +1,2 @@ -rootProject.name = 'LbryAndroid' -include ':@react-native-community_async-storage' -project(':@react-native-community_async-storage').projectDir = new File(settingsDir, '../node_modules/@react-native-community/async-storage/android') -include ':react-native-camera' -project(':react-native-camera').projectDir = new File(settingsDir, '../node_modules/react-native-camera/android') -include ':react-native-exception-handler' -project(':react-native-exception-handler').projectDir = new File(settingsDir, '../node_modules/react-native-exception-handler/android') -include ':react-native-fast-image' -project(':react-native-fast-image').projectDir = new File(settingsDir, '../node_modules/react-native-fast-image/android') -include ':react-native-fs' -project(':react-native-fs').projectDir = new File(settingsDir, '../node_modules/react-native-fs/android') -include ':react-native-gesture-handler' -project(':react-native-gesture-handler').projectDir = new File(settingsDir, '../node_modules/react-native-gesture-handler/android') -include ':react-native-reanimated' -project(':react-native-reanimated').projectDir = new File(settingsDir, '../node_modules/react-native-reanimated/android') -include ':react-native-snackbar' -project(':react-native-snackbar').projectDir = new File(settingsDir, '../node_modules/react-native-snackbar/android') -include ':react-native-video' -project(':react-native-video').projectDir = new File(settingsDir, '../node_modules/react-native-video/android-exoplayer') -include ':react-native-webview' -project(':react-native-webview').projectDir = new File(settingsDir, '../node_modules/react-native-webview/android') -include ':rn-fetch-blob' -project(':rn-fetch-blob').projectDir = new File(settingsDir, '../node_modules/rn-fetch-blob/android') -include ':app' \ No newline at end of file +rootProject.name='LBRY' +include ':app'