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 502a6362..8ad216cf 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 1402
- versionName "0.14.2"
- missingDimensionStrategy 'react-native-camera', 'general'
- multiDexEnabled true
- }
- dexOptions {
- javaMaxHeapSize "2048M"
- preDexLibraries false
- jumboMode true
+ minSdkVersion 21
+ targetSdkVersion 29
+ versionCode 1500
+ versionName "0.15.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
+
productFlavors {
__32bit {
versionCode android.defaultConfig.versionCode * 10 + 1
@@ -167,66 +34,53 @@ android {
}
}
}
- signingConfigs {
- debug {
- storeFile file('debug.keystore')
- storePassword 'android'
- keyAlias 'androiddebugkey'
- keyPassword 'android'
- }
- }
+
buildTypes {
- debug {
- signingConfig signingConfigs.debug
- }
release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
+
}
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 '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'
+
+ 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:flexbox:2.0.1'
+
+ 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.67.1-release__arm.aar')
__64bitImplementation files('libs/lbrysdk-0.67.1-release__arm64.aar')
-
- if (enableHermes) {
- def hermesPath = "../../node_modules/hermes-engine/android/";
- debugImplementation files(hermesPath + "hermes-debug.aar")
- releaseImplementation files(hermesPath + "hermes-release.aar")
- } else {
- implementation jscFlavor
- }
-}
-
-// 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'
}
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/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..fdb62306 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,16 +1,8 @@
+
-
-
@@ -18,68 +10,48 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:windowSoftInputMode="adjustResize">
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
+
\ 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/FileViewActivity.java b/app/src/main/java/io/lbry/browser/FileViewActivity.java
new file mode 100644
index 00000000..29cef886
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/FileViewActivity.java
@@ -0,0 +1,468 @@
+package io.lbry.browser;
+
+import android.app.PictureInPictureParams;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.format.DateUtils;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.widget.NestedScrollView;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+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.util.Util;
+import com.google.android.flexbox.FlexboxLayoutManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.lbry.browser.adapter.ClaimListAdapter;
+import io.lbry.browser.adapter.TagListAdapter;
+import io.lbry.browser.model.Claim;
+import io.lbry.browser.model.ClaimCacheKey;
+import io.lbry.browser.model.File;
+import io.lbry.browser.model.Tag;
+import io.lbry.browser.tasks.ClaimSearchTask;
+import io.lbry.browser.tasks.FileListTask;
+import io.lbry.browser.tasks.LighthouseSearchTask;
+import io.lbry.browser.tasks.ResolveTask;
+import io.lbry.browser.utils.Helper;
+import io.lbry.browser.utils.Lbry;
+
+public class FileViewActivity extends AppCompatActivity {
+
+ public static FileViewActivity instance = null;
+ private static final int RELATED_CONTENT_SIZE = 16;
+
+ private SimpleExoPlayer player;
+ private boolean loadFilePending;
+ private boolean resolving;
+ private Claim claim;
+ private ClaimListAdapter relatedContentAdapter;
+ private File file;
+ private BroadcastReceiver sdkReadyReceiver;
+ private Player.EventListener fileViewPlayerListener;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String claimId = null;
+ String url = null;
+ Intent intent = getIntent();
+ if (intent != null) {
+ claimId = intent.getStringExtra("claimId");
+ url = intent.getStringExtra("url");
+ }
+ if (Helper.isNullOrEmpty(url)) {
+ // This activity should not be opened without a url set
+ finish();
+ return;
+ }
+
+ instance = this;
+ ClaimCacheKey key = new ClaimCacheKey();
+ key.setClaimId(claimId);
+ if (url.contains("#")) {
+ key.setPermanentUrl(url); // use the same url for the key so that we can match the key for any value that's the same
+ key.setCanonicalUrl(url);
+ key.setShortUrl(url);
+ }
+ if (Lbry.claimCache.containsKey(key)) {
+ claim = Lbry.claimCache.get(key);
+ checkAndResetNowPlayingClaim();
+ file = claim.getFile();
+ if (file == null) {
+ loadFile();
+ }
+ }
+ setContentView(R.layout.activity_file_view);
+
+ if (claim == null) {
+ resolveUrl(url);
+ }
+
+ registerSdkReadyReceiver();
+
+ fileViewPlayerListener = new Player.EventListener() {
+ @Override
+ public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
+ /*if (playbackState == Player.STATE_READY) {
+ MainActivity.setNowPlayingClaim(claim, FileViewActivity.this);
+ }*/
+ }
+ };
+
+ initUi();
+ renderClaim();
+ }
+
+ private void checkAndResetNowPlayingClaim() {
+ if (MainActivity.nowPlayingClaim != null &&
+ !MainActivity.nowPlayingClaim.getClaimId().equalsIgnoreCase(claim.getClaimId())) {
+ MainActivity.clearNowPlayingClaim(this);
+ }
+ }
+
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ MainActivity.startingFileViewActivity = false;
+ if (intent != null) {
+ String newClaimId = intent.getStringExtra("claimId");
+ String newUrl = intent.getStringExtra("url");
+
+ String oldClaimId = claim != null ? claim.getClaimId() : null;
+ if (!Helper.isNullOrEmpty(newClaimId)) {
+ if (newClaimId.equalsIgnoreCase(oldClaimId)) {
+ // it's the same claim, so we do nothing
+ if (MainActivity.appPlayer != null) {
+ PlayerView view = findViewById(R.id.file_view_exoplayer_view);
+ view.setPlayer(null);
+ view.setPlayer(MainActivity.appPlayer);
+ }
+ return;
+ }
+
+ ClaimCacheKey key = new ClaimCacheKey();
+ key.setClaimId(newClaimId);
+ if (!Helper.isNullOrEmpty(newUrl) && newUrl.contains("#")) {
+ key.setPermanentUrl(newUrl);
+ key.setCanonicalUrl(newUrl);
+ key.setShortUrl(newUrl);
+ }
+ if (Lbry.claimCache.containsKey(key)) {
+ claim = Lbry.claimCache.get(key);
+ checkAndResetNowPlayingClaim();
+ file = claim.getFile();
+ if (file == null) {
+ loadFile();
+ }
+ renderClaim();
+ } else {
+ findViewById(R.id.file_view_claim_display_area).setVisibility(View.INVISIBLE);
+ resolveUrl(newUrl);
+ }
+ }
+ }
+ }
+
+ private void registerSdkReadyReceiver() {
+ 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
+ if (loadFilePending) {
+ loadFile();
+ }
+ }
+ };
+ registerReceiver(sdkReadyReceiver, filter);
+ }
+
+ private String getStreamingUrl() {
+ if (file != null && !Helper.isNullOrEmpty(file.getStreamingUrl())) {
+ return file.getStreamingUrl();
+ }
+
+ return buildLbryTvStreamingUrl();
+ }
+
+ private String buildLbryTvStreamingUrl() {
+ return String.format("https://player.lbry.tv/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;
+ // TODO: Check if it's paid content and then wait for the user to explicity request the file
+ String claimId = claim.getClaimId();
+ FileListTask task = new FileListTask(claimId, null, new FileListTask.FileListResultHandler() {
+ @Override
+ public void onSuccess(List files) {
+ if (files.size() > 0) {
+ file = files.get(0);
+ claim.setFile(file);
+ }
+ }
+
+ @Override
+ public void onError(Exception error) {
+
+ }
+ });
+ }
+
+ protected void onResume() {
+ super.onResume();
+ MainActivity.startingFileViewActivity = false;
+ }
+
+ private void resolveUrl(String url) {
+ resolving = true;
+ View loadingView = findViewById(R.id.file_view_loading_container);
+ ResolveTask task = new ResolveTask(url, Lbry.LBRY_TV_CONNECTION_STRING, loadingView, new ResolveTask.ResolveResultHandler() {
+ @Override
+ public void onSuccess(List claims) {
+ if (claims.size() > 0) {
+ claim = claims.get(0);
+ checkAndResetNowPlayingClaim();
+ loadFile();
+ renderClaim();
+ }
+ }
+
+ @Override
+ public void onError(Exception error) {
+ resolving = false;
+ }
+ });
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ private void initUi() {
+ findViewById(R.id.file_view_title_area).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ ImageView descIndicator = findViewById(R.id.file_view_desc_toggle_arrow);
+ View descriptionArea = findViewById(R.id.file_view_description_area);
+ if (descriptionArea.getVisibility() != View.VISIBLE) {
+ descriptionArea.setVisibility(View.VISIBLE);
+ descIndicator.setImageResource(R.drawable.ic_arrow_dropup);
+ } else {
+ descriptionArea.setVisibility(View.GONE);
+ descIndicator.setImageResource(R.drawable.ic_arrow_dropdown);
+ }
+ }
+ });
+
+ RecyclerView relatedContentList = findViewById(R.id.file_view_related_content_list);
+ relatedContentList.setNestedScrollingEnabled(false);
+ LinearLayoutManager llm = new LinearLayoutManager(this);
+ relatedContentList.setLayoutManager(llm);
+ }
+
+ private void renderClaim() {
+ if (claim == null) {
+ return;
+ }
+
+ ((NestedScrollView) findViewById(R.id.file_view_scroll_view)).scrollTo(0, 0);
+ findViewById(R.id.file_view_claim_display_area).setVisibility(View.VISIBLE);
+
+ ImageView descIndicator = findViewById(R.id.file_view_desc_toggle_arrow);
+ descIndicator.setImageResource(R.drawable.ic_arrow_dropdown);
+
+ findViewById(R.id.file_view_description_area).setVisibility(View.GONE);
+ ((TextView) findViewById(R.id.file_view_title)).setText(claim.getTitle());
+ ((TextView) findViewById(R.id.file_view_description)).setText(claim.getDescription());
+ ((TextView) findViewById(R.id.file_view_publisher_name)).setText(
+ Helper.isNullOrEmpty(claim.getPublisherName()) ? getString(R.string.anonymous) : claim.getPublisherName());
+
+ RecyclerView descTagsList = findViewById(R.id.file_view_tag_list);
+ FlexboxLayoutManager flm = new FlexboxLayoutManager(this);
+ descTagsList.setLayoutManager(flm);
+
+ List tags = claim.getTagObjects();
+ TagListAdapter tagListAdapter = new TagListAdapter(tags, this);
+ tagListAdapter.setClickListener(new TagListAdapter.TagClickListener() {
+ @Override
+ public void onTagClicked(Tag tag) {
+ Intent intent = new Intent(MainActivity.ACTION_OPEN_ALL_CONTENT_TAG);
+ intent.putExtra("tag", tag.getName());
+ sendBroadcast(intent);
+ moveTaskToBack(true);
+ }
+ });
+ descTagsList.setAdapter(tagListAdapter);
+ findViewById(R.id.file_view_tag_area).setVisibility(tags.size() > 0 ? View.VISIBLE : View.GONE);
+
+ Claim.GenericMetadata metadata = claim.getValue();
+ if (metadata instanceof Claim.StreamMetadata) {
+ Claim.StreamMetadata streamMetadata = (Claim.StreamMetadata) metadata;
+ long publishTime = streamMetadata.getReleaseTime() > 0 ? streamMetadata.getReleaseTime() * 1000 : claim.getTimestamp() * 1000;
+ ((TextView) findViewById(R.id.file_view_publish_time)).setText(DateUtils.getRelativeTimeSpanString(
+ publishTime, System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE));
+
+ // Check the metadata type
+ String mediaType = streamMetadata.getSource().getMediaType();
+ // Use Exoplayer view if it's video / audio
+ if (mediaType.startsWith("audio") || mediaType.startsWith("video")) {
+ showExoplayerView();
+ playMedia();
+ } else if (mediaType.startsWith("text")) {
+
+ } else if (mediaType.startsWith("image")) {
+
+ } else {
+ // unsupported type
+ showUnsupportedView();
+ }
+ }
+
+ loadRelatedContent();
+ }
+
+ private void showUnsupportedView() {
+ findViewById(R.id.file_view_exoplayer_container).setVisibility(View.GONE);
+
+ findViewById(R.id.file_view_unsupported_container).setVisibility(View.VISIBLE);
+ }
+
+ private void showExoplayerView() {
+ findViewById(R.id.file_view_unsupported_container).setVisibility(View.GONE);
+
+ findViewById(R.id.file_view_exoplayer_container).setVisibility(View.VISIBLE);
+ }
+
+ private void playMedia() {
+ boolean newPlayerCreated = false;
+ if (MainActivity.appPlayer == null) {
+ MainActivity.appPlayer = new SimpleExoPlayer.Builder(this).build();
+ MainActivity.appPlayer.setPlayWhenReady(true);
+ MainActivity.appPlayer.addListener(fileViewPlayerListener);
+
+ newPlayerCreated = true;
+ }
+
+ PlayerView view = findViewById(R.id.file_view_exoplayer_view);
+ PlayerControlView controlView = findViewById(R.id.file_view_exoplayer_control_view);
+ view.setPlayer(MainActivity.appPlayer);
+ controlView.setPlayer(MainActivity.appPlayer);
+
+ 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;
+ }
+
+ MainActivity.setNowPlayingClaim(claim, FileViewActivity.this);
+ String userAgent = Util.getUserAgent(this, getString(R.string.app_name));
+ MediaSource mediaSource = new ProgressiveMediaSource.Factory(
+ new DefaultDataSourceFactory(this, userAgent),
+ new DefaultExtractorsFactory()
+ ).createMediaSource(Uri.parse(getStreamingUrl()));
+ MainActivity.appPlayer.prepare(mediaSource, true, true);
+ }
+
+ private void loadRelatedContent() {
+ // reset the list view
+ ((RecyclerView) findViewById(R.id.file_view_related_content_list)).setAdapter(null);
+
+ String title = claim.getTitle();
+ String claimId = claim.getClaimId();
+ ProgressBar relatedLoading = findViewById(R.id.file_view_related_content_progress);
+ LighthouseSearchTask relatedTask = new LighthouseSearchTask(title, RELATED_CONTENT_SIZE, 0, false, claimId, relatedLoading, new ClaimSearchTask.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, FileViewActivity.this);
+ relatedContentAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() {
+ @Override
+ public void onClaimClicked(Claim claim) {
+ Intent intent = new Intent(FileViewActivity.this, FileViewActivity.class);
+ intent.putExtra("claimId", claim.getClaimId());
+ intent.putExtra("url", claim.getPermanentUrl());
+ MainActivity.startingFileViewActivity = true;
+ startActivity(intent);
+ }
+ });
+
+ RecyclerView relatedContentList = findViewById(R.id.file_view_related_content_list);
+ relatedContentList.setAdapter(relatedContentAdapter);
+ relatedContentAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onError(Exception error) {
+
+ }
+ });
+ relatedTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ public void onBackPressed() {
+ MainActivity.mainActive = true;
+ Intent intent = new Intent(this, MainActivity.class);
+ startActivity(intent);
+ finish();
+ }
+
+ protected void onUserLeaveHint() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !MainActivity.mainActive) {
+ PictureInPictureParams params = new PictureInPictureParams.Builder().build();
+ enterPictureInPictureMode(params);
+ }
+ }
+
+ protected void onStop() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (isInPictureInPictureMode() && MainActivity.appPlayer != null) {
+ MainActivity.appPlayer.setPlayWhenReady(false);
+ }
+ }
+ super.onStop();
+ }
+
+ protected void onDestroy() {
+ Helper.unregisterReceiver(sdkReadyReceiver, this);
+ if (MainActivity.appPlayer != null && fileViewPlayerListener != null) {
+ MainActivity.appPlayer.removeListener(fileViewPlayerListener);
+ }
+ instance = null;
+ super.onDestroy();
+ }
+
+ private void renderPictureInPictureMode() {
+ findViewById(R.id.file_view_scroll_view).setVisibility(View.GONE);
+ findViewById(R.id.file_view_exoplayer_control_view).setVisibility(View.GONE);
+ }
+ private void renderFullMode() {
+ findViewById(R.id.file_view_scroll_view).setVisibility(View.VISIBLE);
+
+ PlayerControlView controlView = findViewById(R.id.file_view_exoplayer_control_view);
+ controlView.setPlayer(null);
+ controlView.setPlayer(MainActivity.appPlayer);
+ }
+
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
+ if (isInPictureInPictureMode) {
+ renderPictureInPictureMode();
+ } else {
+ renderFullMode();
+ }
+ }
+}
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..0aa9a0d2
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/FirstRunActivity.java
@@ -0,0 +1,128 @@
+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.Html;
+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.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();
+ }
+ }
+
+ 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
+
+ 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_FIRST_RUN_COMPLETED, true).apply();
+
+ // first_run_completed event
+
+ 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
deleted file mode 100644
index f5c4c4a8..00000000
--- a/app/src/main/java/io/lbry/browser/LbrynetMessagingService.java
+++ /dev/null
@@ -1,189 +0,0 @@
-package io.lbry.browser;
-
-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.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import androidx.core.app.NotificationCompat;
-import androidx.core.content.ContextCompat;
-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.lbrysdk.LbrynetService;
-import io.lbry.browser.reactmodules.UtilityModule;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.List;
-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);
- }
-
- Map payload = remoteMessage.getData();
- if (payload != null) {
- String type = payload.get("type");
- String url = payload.get("target");
- String title = payload.get("title");
- String body = payload.get("body");
- String name = payload.get("name"); // notification name
- String contentTitle = payload.get("content_title");
- String channelUrl = payload.get("channel_url");
- //String publishTime = payload.get("publish_time");
- String publishTime = null;
-
- if (type != null && getEnabledTypes().indexOf(type) > -1 && body != null && body.trim().length() > 0) {
- // only log the receive event for valid notifications received
- if (firebaseAnalytics != null) {
- Bundle bundle = new Bundle();
- bundle.putString("name", name);
- firebaseAnalytics.logEvent("lbry_notification_receive", bundle);
- }
-
- sendNotification(title, body, type, url, name, contentTitle, channelUrl, publishTime);
- }
- }
- }
-
- @Override
- public void onNewToken(String token) {
- Log.d(TAG, "Refreshed token: " + token);
-
- // If you want to send messages to this application instance or
- // manage this apps subscriptions on the server side, send the
- // Instance ID token to your app server.
- sendRegistrationToServer(token);
- }
-
- /**
- * Persist token to third-party servers.
- *
- * Modify this method to associate the user's FCM InstanceID token with any server-side account
- * maintained by your application.
- *
- * @param token The new token.
- */
- private void sendRegistrationToServer(String token) {
- // 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.
- */
- private void sendNotification(String title, String messageBody, String type, String url, String name,
- String contentTitle, String channelUrl, String publishTime) {
- //Intent intent = new Intent(this, MainActivity.class);
- //intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- if (url == null) {
- if (TYPE_REWARD.equals(type)) {
- url = "lbry://?rewards";
- } else {
- // 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));
- launchIntent.putExtra("notification_name", name);
- launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, launchIntent, PendingIntent.FLAG_ONE_SHOT);
-
- Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
- NotificationCompat.Builder notificationBuilder =
- new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
- .setColor(ContextCompat.getColor(this, R.color.lbryGreen))
- .setSmallIcon(R.drawable.ic_lbry)
- .setContentTitle(title)
- .setContentText(messageBody)
- .setAutoCancel(true)
- .setSound(defaultSoundUri)
- .setContentIntent(pendingIntent);
-
- NotificationManager notificationManager =
- (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-
- // 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);
- notificationManager.createNotificationChannel(channel);
- }
-
- notificationManager.notify(9898, notificationBuilder.build());
- }
-
- public List getEnabledTypes() {
- SharedPreferences sp = getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
- List enabledTypes = new ArrayList();
-
- if (sp.getBoolean(UtilityModule.RECEIVE_SUBSCRIPTION_NOTIFICATIONS, true)) {
- enabledTypes.add(TYPE_SUBSCRIPTION);
- }
- if (sp.getBoolean(UtilityModule.RECEIVE_REWARD_NOTIFICATIONS, true)) {
- enabledTypes.add(TYPE_REWARD);
- }
- if (sp.getBoolean(UtilityModule.RECEIVE_INTERESTS_NOTIFICATIONS, true)) {
- enabledTypes.add(TYPE_INTERESTS);
- }
- if (sp.getBoolean(UtilityModule.RECEIVE_CREATOR_NOTIFICATIONS, true)) {
- enabledTypes.add(TYPE_CREATOR);
- }
-
- return enabledTypes;
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/io/lbry/browser/LocalFileProvider.java b/app/src/main/java/io/lbry/browser/LocalFileProvider.java
deleted file mode 100644
index 87964c27..00000000
--- a/app/src/main/java/io/lbry/browser/LocalFileProvider.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package io.lbry.browser;
-
-import androidx.core.content.FileProvider;
-
-public class LocalFileProvider extends FileProvider {
-
-}
diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java
index f5b0d6f0..babf6108 100644
--- a/app/src/main/java/io/lbry/browser/MainActivity.java
+++ b/app/src/main/java/io/lbry/browser/MainActivity.java
@@ -1,266 +1,1114 @@
package io.lbry.browser;
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
+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.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.net.Uri;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+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 androidx.core.app.ActivityCompat;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Base64;
+import android.util.Log;
+import android.view.View;
+import android.view.Menu;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.ui.PlayerView;
+import com.google.android.material.snackbar.Snackbar;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.app.AppCompatDelegate;
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.res.ResourcesCompat;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.core.view.GravityCompat;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.navigation.ui.AppBarConfiguration;
+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.FileInputStream;
+import java.io.InputStreamReader;
+import java.net.ConnectException;
+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.adapter.NavigationMenuAdapter;
+import io.lbry.browser.adapter.UrlSuggestionListAdapter;
+import io.lbry.browser.data.DatabaseHelper;
+import io.lbry.browser.exceptions.ApiCallException;
+import io.lbry.browser.listener.SdkStatusListener;
+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.UrlSuggestion;
+import io.lbry.browser.model.WalletBalance;
+import io.lbry.browser.model.WalletSync;
+import io.lbry.browser.model.lbryinc.Subscription;
+import io.lbry.browser.tasks.LighthouseAutoCompleteTask;
+import io.lbry.browser.tasks.ResolveTask;
+import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler;
+import io.lbry.browser.tasks.wallet.SyncGetTask;
+import io.lbry.browser.tasks.wallet.SyncTaskHandler;
+import io.lbry.browser.tasks.wallet.WalletBalanceTask;
+import io.lbry.browser.ui.BaseFragment;
+import io.lbry.browser.ui.channel.ChannelFragment;
+import io.lbry.browser.ui.following.FollowingFragment;
+import io.lbry.browser.ui.search.SearchFragment;
+import io.lbry.browser.ui.settings.SettingsFragment;
+import io.lbry.browser.ui.allcontent.AllContentFragment;
+import io.lbry.browser.ui.wallet.WalletFragment;
+import io.lbry.browser.utils.Helper;
+import io.lbry.browser.utils.Lbry;
+import io.lbry.browser.utils.LbryUri;
+import io.lbry.browser.utils.Lbryio;
import io.lbry.lbrysdk.LbrynetService;
import io.lbry.lbrysdk.ServiceHelper;
import io.lbry.lbrysdk.Utils;
+import lombok.Getter;
-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;
+ public static SimpleExoPlayer appPlayer;
+ public static Claim nowPlayingClaim;
+ public static boolean startingFileViewActivity = false;
+ public static boolean mainActive = false;
+ private boolean enteringPIPMode = false;
-public class MainActivity extends FragmentActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
+ private Map openNavFragments;
+ private static final Map fragmentClassNavIdMap = new HashMap<>();
+ static {
+ fragmentClassNavIdMap.put(FollowingFragment.class, NavMenuItem.ID_ITEM_FOLLOWING);
+ fragmentClassNavIdMap.put(WalletFragment.class, NavMenuItem.ID_ITEM_WALLET);
+ fragmentClassNavIdMap.put(SettingsFragment.class, NavMenuItem.ID_ITEM_SETTINGS);
+ fragmentClassNavIdMap.put(AllContentFragment.class, NavMenuItem.ID_ITEM_ALL_CONTENT);
- 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();
+ // Internal (sub-)pages
+ fragmentClassNavIdMap.put(ChannelFragment.class, NavMenuItem.ID_ITEM_FOLLOWING);
+ fragmentClassNavIdMap.put(SearchFragment.class, NavMenuItem.ID_ITEM_FOLLOWING);
+ }
- private BroadcastReceiver notificationsReceiver;
- private BroadcastReceiver smsReceiver;
+ public static final int REQUEST_SIMPLE_SIGN_IN = 2001;
+ public static final int REQUEST_WALLET_SYNC_SIGN_IN = 2002;
+
+ // 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";
+
+ // preference keys
+ public static final String PREFERENCE_KEY_DARK_MODE = "io.lbry.browser.preference.userinterface.DarkMode";
+ public static final String PREFERENCE_KEY_NOTIFICATION_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_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";
+
+ private final int CHECK_SDK_READY_INTERVAL = 1000;
+
+ public static final String PREFERENCE_KEY_FIRST_RUN_COMPLETED = "io.lbry.browser.Preference.FirstRunCompleted";
+ 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;
+
+ // broadcast receivers
private BroadcastReceiver serviceActionsReceiver;
- private BroadcastReceiver downloadEventReceiver;
- private FirebaseAnalytics firebaseAnalytics;
- private ReactRootView mReactRootView;
- private ReactInstanceManager mReactInstanceManager;
+ private BroadcastReceiver requestsReceiver;
+ private BroadcastReceiver userActionsReceiver;
- /**
- * Flag which indicates whether or not the service is running. Will be updated in the
- * onResume method.
- */
+ private boolean userAuthenticated = false;
+
+ private boolean appStarted;
private boolean serviceRunning;
private CheckSdkReadyTask checkSdkReadyTask;
private boolean receivedStopService;
- private PermissionListener permissionListener;
- public static boolean lbrySdkReady;
+ private AppBarConfiguration mAppBarConfiguration;
+ private ActionBarDrawerToggle toggle;
+ @Getter
+ private DatabaseHelper dbHelper;
+ private int selectedMenuItemId = -1;
+ private List sdkStatusListeners;
+ private List walletBalanceListeners;
+ @Getter
+ private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+ private boolean walletBalanceUpdateScheduled;
+ private String pendingAllContentTag;
+ private String pendingChannelUrl;
+ private boolean pendingFollowingReload;
+
+ private final List supportedMenuItemIds = Arrays.asList(
+ NavMenuItem.ID_ITEM_FOLLOWING, NavMenuItem.ID_ITEM_ALL_CONTENT, NavMenuItem.ID_ITEM_WALLET, NavMenuItem.ID_ITEM_SETTINGS
+ );
- protected String getMainComponentName() {
- return "LBRYApp";
- }
-
- public static LaunchTiming CurrentLaunchTiming;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
- CurrentLaunchTiming = new LaunchTiming(new Date());
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
+ boolean darkMode = sp.getBoolean(PREFERENCE_KEY_DARK_MODE, false);
+ AppCompatDelegate.setDefaultNightMode(darkMode ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
+
+ initKeyStore();
+ loadAuthToken();
+
+ dbHelper = new DatabaseHelper(this);
+ if (!darkMode) {
+ getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ }
+
super.onCreate(savedInstanceState);
- currentActivity = this;
+ setContentView(R.layout.activity_main);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
- SoLoader.init(this, false);
-
- // Register the stop service receiver (so that we close the activity if the user requests the service to stop)
+ // register receivers
+ registerRequestsReceiver();
registerServiceActionsReceiver();
+ registerUserActionsReceiver();
- // Register SMS receiver for handling verification texts
- registerSmsReceiver();
+ // setup uri bar
+ setupUriBar();
- // Register the receiver to emit download events
- registerDownloadEventReceiver();
+ // other
+ openNavFragments = new HashMap<>();
+ sdkStatusListeners = new ArrayList<>();
+ walletBalanceListeners = new ArrayList<>();
- // 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));
- serviceRunning = isServiceRunning(this, LbrynetService.class);
- if (!serviceRunning) {
- CurrentLaunchTiming.setColdStart(true);
- ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice");
- }
- checkSdkReady();
+ sdkStatusListeners.add(this);
- checkNotificationOpenIntent(getIntent());
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ fragmentManager.addOnBackStackChangedListener(backStackChangedListener);
- 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);
-
- registerNotificationsReceiver();
-
- setContentView(mReactRootView);
- }
-
- private void checkSdkReady() {
- if (!lbrySdkReady) {
- 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);
- }
- }, 1000);
- }
- }
-
- private void checkNotificationOpenIntent(Intent intent) {
- if (intent != null) {
- String notificationName = intent.getStringExtra("notification_name");
- if (notificationName != null) {
- logNotificationOpen(notificationName);
- }
- }
- }
-
- 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);
- }
-
- 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
+ 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;
}
}
};
- registerReceiver(downloadEventReceiver, intentFilter);
+ 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();
+ 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) {
+ Intent intent = new Intent(MainActivity.this, FileViewActivity.class);
+ intent.putExtra("claimId", nowPlayingClaim.getClaimId());
+ intent.putExtra("url", nowPlayingClaim.getPermanentUrl());
+ startingFileViewActivity = true;
+ startActivity(intent);
+ }
+ }
+ });
+
+ // 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() && menuItem.getId() != NavMenuItem.ID_ITEM_ALL_CONTENT) {
+ // already open
+ navMenuAdapter.setCurrentItem(menuItem);
+ closeDrawer();
+ return;
+ }
+
+ if (!supportedMenuItemIds.contains(menuItem.getId())) {
+ Snackbar.make(navItemsView, R.string.not_yet_implemented, Snackbar.LENGTH_LONG).show();
+ } else {
+ navMenuAdapter.setCurrentItem(menuItem);
+ shouldOpenUserSelectedMenuItem = true;
+ selectedMenuItemId = menuItem.getId();
+ }
+ closeDrawer();
+ }
+ });
+ navItemsView.setAdapter(navMenuAdapter);
+
+ findViewById(R.id.sign_in_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ walletSyncSignIn();
+ }
+ });
+ }
+
+ private boolean shouldOpenUserSelectedMenuItem;
+
+ public void addSdkStatusListener(SdkStatusListener listener) {
+ if (!sdkStatusListeners.contains(listener)) {
+ sdkStatusListeners.add(listener);
+ }
+ }
+
+ public void removeSdkStatusListener(SdkStatusListener listener) {
+ sdkStatusListeners.remove(listener);
+ }
+
+ public void addWalletBalanceListener(WalletBalanceListener listener) {
+ if (!walletBalanceListeners.contains(listener)) {
+ walletBalanceListeners.add(listener);
+ }
+ }
+
+ public void removeWalletBalanceListener(WalletBalanceListener listener) {
+ walletBalanceListeners.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_ALL_CONTENT:
+ openFragment(AllContentFragment.class, true, NavMenuItem.ID_ITEM_ALL_CONTENT);
+ break;
+ case NavMenuItem.ID_ITEM_WALLET:
+ openFragment(WalletFragment.class, true, NavMenuItem.ID_ITEM_WALLET);
+ break;
+
+ case NavMenuItem.ID_ITEM_SETTINGS:
+ openFragment(SettingsFragment.class, true, NavMenuItem.ID_ITEM_SETTINGS);
+ break;
+ }
+ }
+
+ public void openChannelClaim(Claim claim) {
+ Map params = new HashMap<>();
+ params.put("url", claim.getPermanentUrl());
+ params.put("claim", getCachedClaimForUrl(claim.getPermanentUrl()));
+ openFragment(ChannelFragment.class, true, NavMenuItem.ID_ITEM_FOLLOWING, params);
+ setWunderbarValue(claim.getShortUrl());
+ }
+
+ 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);
+ setWunderbarValue(url);
+ }
+
+ private Claim getCachedClaimForUrl(String url) {
+ ClaimCacheKey key = new ClaimCacheKey();
+ key.setCanonicalUrl(url);
+ key.setPermanentUrl(url);
+ key.setShortUrl(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);
+ }
+
+ private void openAllContentFragmentWithTag(String tag) {
+ Map params = new HashMap<>();
+ params.put("singleTag", tag);
+ openFragment(AllContentFragment.class, true, NavMenuItem.ID_ITEM_ALL_CONTENT, params);
+ }
+
+ public static void openFileUrl(String url, Context context) {
+ Intent intent = new Intent(context, FileViewActivity.class);
+ intent.putExtra("url", url);
+ startingFileViewActivity = true;
+ context.startActivity(intent);
+ }
+
+ public static void openFileClaim(Claim claim, Context context) {
+ Intent intent = new Intent(context, FileViewActivity.class);
+ intent.putExtra("claimId", claim.getClaimId());
+ intent.putExtra("url", claim.getPermanentUrl());
+ startingFileViewActivity = true;
+ context.startActivity(intent);
+ }
+
+ 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.global_now_playing_card).setVisibility(View.GONE);
+ getSupportActionBar().hide();
+
+ PlayerView pipPlayer = findViewById(R.id.pip_player);
+ pipPlayer.setVisibility(View.VISIBLE);
+ pipPlayer.setPlayer(appPlayer);
+ }
+ private void renderFullMode() {
+ getSupportActionBar().show();
+ findViewById(R.id.content_main).setVisibility(View.VISIBLE);
+ findViewById(R.id.global_now_playing_card).setVisibility(View.VISIBLE);
+
+ PlayerView pipPlayer = findViewById(R.id.pip_player);
+ pipPlayer.setVisibility(View.INVISIBLE);
+ pipPlayer.setPlayer(null);
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceivers();
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
+ if (receivedStopService || !isServiceRunning(this, LbrynetService.class)) {
+ notificationManager.cancelAll();
+ }
+ if (dbHelper != null) {
+ dbHelper.close();
+ }
+ stopExoplayer();
+ super.onDestroy();
+ }
+
+ private static void stopExoplayer() {
+ if (appPlayer != null) {
+ appPlayer.stop(true);
+ appPlayer.release();
+ appPlayer = 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;
+ }
+
+ @Override
+ public void onError(Exception error) {
+
+ }
+ });
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mainActive = true;
+
+ checkFirstRun();
+ checkNowPlaying();
+
+ // check (and start) the LBRY SDK service
+ serviceRunning = isServiceRunning(this, LbrynetService.class);
+ if (!serviceRunning) {
+ ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice");
+ }
+ checkSdkReady();
+ showSignedInUser();
+
+ if (!Helper.isNullOrEmpty(pendingAllContentTag)) {
+ openAllContentFragmentWithTag(pendingAllContentTag);
+ pendingAllContentTag = null;
+ }
+ if (pendingFollowingReload) {
+ loadFollowingContent();
+ pendingFollowingReload = false;
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ mainActive = false;
+ super.onPause();
+ }
+
+ private void toggleUrlSuggestions(boolean visible) {
+ View container = findViewById(R.id.url_suggestions_container);
+ View closeIcon = findViewById(R.id.wunderbar_close);
+ container.setVisibility(visible ? View.VISIBLE : View.GONE);
+ closeIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ private void setupUriBar() {
+ findViewById(R.id.wunderbar_close).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ findViewById(R.id.wunderbar).clearFocus();
+ InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ });
+ findViewById(R.id.wunderbar).setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View view, boolean hasFocus) {
+ toggleUrlSuggestions(hasFocus);
+ }
+ });
+
+ ((EditText) findViewById(R.id.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) {
+ handleUriInputChanged(charSequence.toString().trim());
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+
+ }
+ });
+
+ 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
+ break;
+ case UrlSuggestion.TYPE_FILE:
+ Context context = MainActivity.this;
+ if (urlSuggestion.getClaim() != null) {
+ openFileClaim(urlSuggestion.getClaim(), context);
+ } else {
+ openFileUrl(urlSuggestion.getUri().toString(), context);
+ }
+ break;
+ case UrlSuggestion.TYPE_SEARCH:
+ launchSearch(urlSuggestion.getText());
+ break;
+ case UrlSuggestion.TYPE_TAG:
+ // open tag page
+ break;
+ }
+ findViewById(R.id.wunderbar).clearFocus();
+ //findViewById(R.id.url_suggestions_container).setVisibility(View.GONE);
+ }
+ });
+
+ RecyclerView urlSuggestionList = findViewById(R.id.url_suggestions);
+ LinearLayoutManager llm = new LinearLayoutManager(this);
+ urlSuggestionList.setLayoutManager(llm);
+ urlSuggestionList.setAdapter(urlSuggestionListAdapter);
+ }
+
+ 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 ResolveTask.ResolveResultHandler() {
+ @Override
+ public void onSuccess(List claims) {
+ for (int i = 0; i < claims.size(); i++) {
+ // build a simple url from the claim for matching
+ Claim claim = claims.get(i);
+ if (Helper.isNullOrEmpty(claim.getName())) {
+ continue;
+ }
+
+ LbryUri simpleUrl = new LbryUri();
+ if (claim.getName().startsWith("@")) {
+ // channel
+ simpleUrl.setChannelName(claim.getName());
+ } else {
+ simpleUrl.setStreamName(claim.getName());
+ }
+
+ urlSuggestionListAdapter.setClaimForUrl(simpleUrl, claim);
+ }
+ urlSuggestionListAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onError(Exception error) {
+
+ }
+ });
+ task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ private void handleUriInputChanged(String text) {
+ // build the default suggestions
+ urlSuggestionListAdapter.clear();
+ if (Helper.isNullOrEmpty(text)) {
+ return;
+ }
+
+ List defaultSuggestions = buildDefaultSuggestions(text);
+ urlSuggestionListAdapter.addUrlSuggestions(defaultSuggestions);
+
+ LighthouseAutoCompleteTask task = new LighthouseAutoCompleteTask(text, null, new LighthouseAutoCompleteTask.AutoCompleteResultHandler() {
+ @Override
+ public void onSuccess(List suggestions) {
+ urlSuggestionListAdapter.addUrlSuggestions(suggestions);
+
+ List urls = urlSuggestionListAdapter.getItemUrls();
+ resolveUrlSuggestions(urls);
+ }
+
+ @Override
+ public void onError(Exception error) {
+
+ }
+ });
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ private List buildDefaultSuggestions(String text) {
+ List suggestions = new ArrayList();
+
+ // First item is always search
+ UrlSuggestion searchSuggestion = new UrlSuggestion(UrlSuggestion.TYPE_SEARCH, text);
+ suggestions.add(searchSuggestion);
+
+ if (!text.matches(LbryUri.REGEX_INVALID_URI)) {
+ boolean isChannel = text.startsWith("@");
+ if (!isChannel) {
+ LbryUri 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 and tags should not contain spaces
+ if (isChannel) {
+ LbryUri uri = new LbryUri();
+ uri.setChannelName(text);
+ UrlSuggestion suggestion = new UrlSuggestion(UrlSuggestion.TYPE_CHANNEL, text);
+ suggestion.setUri(uri);
+ suggestions.add(suggestion);
+ } else {
+ UrlSuggestion suggestion = new UrlSuggestion(UrlSuggestion.TYPE_TAG, text);
+ suggestions.add(suggestion);
+ }
+ }
+ }
+
+ return suggestions;
+ }
+
+ private void checkNowPlaying() {
+ 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);
+ }
+ }
+
+ 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_FIRST_RUN_COMPLETED, false);
+ if (!firstRunCompleted) {
+ startActivity(new Intent(this, FirstRunActivity.class));
+ } else if (!appStarted) {
+ // first run completed, startup
+ startup();
+ }
+ }
+
+ 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
+ android.util.Log.e(TAG, "Could not decrypt existing auth token.", ex);
+ }
+ }
+ }
+
+ private void checkSdkReady() {
+ 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();
+ }
+ }
+
+ public void onSdkReady() {
+ if (Lbryio.isSignedIn()) {
+ checkSyncedWallet();
+ }
+ scheduleWalletBalanceUpdate();
+ }
+
+ private void scheduleWalletBalanceUpdate() {
+ if (scheduler != null && !walletBalanceUpdateScheduled) {
+ scheduler.scheduleAtFixedRate(new Runnable() {
+ @Override
+ public void run() {
+ updateWalletBalance();
+ }
+ }, 0, 5, TimeUnit.SECONDS);
+ walletBalanceUpdateScheduled = true;
+ }
+ }
+
+ private void registerRequestsReceiver() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_AUTH_TOKEN_GENERATED);
+ intentFilter.addAction(ACTION_OPEN_ALL_CONTENT_TAG);
+ intentFilter.addAction(ACTION_USER_SIGN_IN_SUCCESS);
+ 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_OPEN_ALL_CONTENT_TAG.equalsIgnoreCase(action)) {
+ handleOpenContentTag(intent);
+ } else if (ACTION_USER_SIGN_IN_SUCCESS.equalsIgnoreCase(action)) {
+ handleUserSignInSuccess(intent);
+ }
+ }
+
+ 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(String url) {
+ pendingChannelUrl = url;
+ }
+ };
+ registerReceiver(requestsReceiver, intentFilter);
+ }
+
+ private void loadFollowingContent() {
+ for (Fragment fragment : openNavFragments.values()) {
+ if (fragment instanceof FollowingFragment) {
+ ((FollowingFragment) fragment).loadFollowing();
+ }
+ }
+ }
+
+ private void registerUserActionsReceiver() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_NOW_PLAYING_CLAIM_UPDATED);
+ intentFilter.addAction(ACTION_NOW_PLAYING_CLAIM_CLEARED);
+ userActionsReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (ACTION_NOW_PLAYING_CLAIM_UPDATED.equals(action)) {
+ handleNowPlayingClaimUpdated();
+ } else if (ACTION_NOW_PLAYING_CLAIM_CLEARED.equals(action)) {
+ handleNowPlayingClaimCleared();
+ }
+ }
+
+ private void handleNowPlayingClaimUpdated() {
+ if (nowPlayingClaim != null) {
+ ((TextView) findViewById(R.id.global_now_playing_title)).setText(nowPlayingClaim.getTitle());
+ ((TextView) findViewById(R.id.global_now_playing_channel_title)).setText(nowPlayingClaim.getPublisherTitle());
+ }
+ }
+
+ private void handleNowPlayingClaimCleared() {
+ 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);
+ }
+ };
+ registerReceiver(userActionsReceiver, intentFilter);
+ }
+
+ @Override
+ public void onBackPressed() {
+ DrawerLayout drawer = findViewById(R.id.drawer_layout);
+ if (drawer.isDrawerOpen(GravityCompat.START)) {
+ drawer.closeDrawer(GravityCompat.START);
+ } else {
+ // 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);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
+ // user signed in
+ showSignedInUser();
+
+ if (requestCode == REQUEST_WALLET_SYNC_SIGN_IN) {
+ for (Fragment fragment : openNavFragments.values()) {
+ if (fragment instanceof WalletFragment) {
+ ((WalletFragment) fragment).onWalletSyncEnabled();
+ }
+ }
+ }
+ }
+ }
+
+ private void showSignedInUser() {
+ if (Lbryio.isSignedIn()) {
+ findViewById(R.id.sign_in_button).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 startup() {
+ final Context context = this;
+ // perform some tasks before launching
+ (new AsyncTask() {
+ protected void onPreExecute() {
+ hideActionBar();
+ lockDrawer();
+ findViewById(R.id.splash_view).setVisibility(View.VISIBLE);
+ }
+ 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?)
+ return false;
+ }
+
+ // load the exchange rate
+ if (Lbryio.LBCUSDRate == 0) {
+ Lbryio.loadExchangeRate();
+ }
+
+ Lbry.INSTALLATION_ID = installId;
+ if (Lbryio.currentUser == null) {
+ Lbryio.authenticate(context);
+ }
+ Lbryio.newInstall(context);
+
+ // (light) fetch subscriptions
+ if (Lbryio.cacheSubscriptions.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.cacheSubscriptions = subscriptions;
+
+ // resolve subscriptions
+ if (subUrls.size() > 0 && Lbryio.cacheResolvedSubscriptions.size() != Lbryio.cacheSubscriptions.size()) {
+ List resolvedSubs = Lbry.resolve(subUrls, Lbry.LBRY_TV_CONNECTION_STRING);
+ Lbryio.cacheResolvedSubscriptions = resolvedSubs;
+ }
+ }
+ }
+ } 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) {
+ Toast.makeText(context, R.string.startup_failed, Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+
+ findViewById(R.id.splash_view).setVisibility(View.GONE);
+ unlockDrawer();
+ showActionBar();
+
+ if (navMenuAdapter != null) {
+ navMenuAdapter.setCurrentItem(NavMenuItem.ID_ITEM_FOLLOWING);
+ }
+
+ loadLastFragment();
+ showSignedInUser();
+
+ appStarted = true;
+ }
+ }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ 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() {
+ enterPIPMode();
+ }
+
+ protected boolean enterPIPMode() {
+ if (enteringPIPMode) {
+ return true;
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
+ appPlayer != null &&
+ FileViewActivity.instance == null &&
+ !startingFileViewActivity) {
+ 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 registerServiceActionsReceiver() {
@@ -290,6 +1138,12 @@ public class MainActivity extends FragmentActivity implements DefaultHardwareBac
registerReceiver(serviceActionsReceiver, intentFilter);
}
+ private void unregisterReceivers() {
+ Helper.unregisterReceiver(requestsReceiver, this);
+ Helper.unregisterReceiver(serviceActionsReceiver, this);
+ Helper.unregisterReceiver(userActionsReceiver, this);
+ }
+
private Notification buildServiceNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, LbrynetService.NOTIFICATION_CHANNEL_ID);
Intent contextIntent = new Intent(this, MainActivity.class);
@@ -312,556 +1166,73 @@ 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_YOUR_TAGS, R.string.fa_hashtag, R.string.your_tags, "YourTags", 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 static Activity getActivity() {
- Activity activity = new Activity();
- activity = currentActivity;
- return activity;
+ public static void setNowPlayingClaim(Claim claim, Context context) {
+ nowPlayingClaim = claim;
+ context.sendBroadcast(new Intent(ACTION_NOW_PLAYING_CLAIM_UPDATED));
}
- @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 static void clearNowPlayingClaim(Context context) {
+ nowPlayingClaim = null;
+ context.sendBroadcast(new Intent(ACTION_NOW_PLAYING_CLAIM_CLEARED));
}
- @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 +1241,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("stream_manager") && startupStatus.getBoolean("wallet");
}
} catch (ConnectException ex) {
// pass
@@ -894,37 +1258,167 @@ 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);
+ }
+
+ @Override
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
+ 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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (!MainActivity.startingFileViewActivity && appPlayer != null && isInPictureInPictureMode()) {
+ 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) {
+ 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);
+ 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 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;
- }
+
+ 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);
}
}
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..29342786
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/VerificationActivity.java
@@ -0,0 +1,152 @@
+package io.lbry.browser;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.viewpager.widget.ViewPager;
+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.FetchCurrentUserTask;
+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));
+
+ if (Lbryio.isSignedIn() && flow == VERIFICATION_FLOW_WALLET) {
+ viewPager.setCurrentItem(1);
+ }
+
+ 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();
+ }
+ });
+ }
+
+ @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);
+ }
+ 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));
+
+ 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
+ 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) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ // change pager view depending on flow
+ FetchCurrentUserTask task = new FetchCurrentUserTask(new FetchCurrentUserTask.FetchUserTaskHandler() {
+ @Override
+ public void onSuccess(User user) { Lbryio.currentUser = user; }
+ @Override
+ public void onError(Exception error) { }
+ });
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+
+ ViewPager2 viewPager = findViewById(R.id.verification_pager);
+ // for rewards, (show phone verification if not done, or manual verification if required)
+
+ // for wallet sync, if password unlock is required, show password entry page
+ viewPager.setCurrentItem(1);
+ }
+ }
+
+ @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..d463d97c
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/adapter/ChannelFilterListAdapter.java
@@ -0,0 +1,119 @@
+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
+ 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 TextView allTextView;
+ 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);
+ allTextView = v.findViewById(R.id.channel_filter_all);
+ }
+ }
+
+ public int getItemCount() {
+ return items != null ? items.size() : 0;
+ }
+
+ public boolean isClaimSelected(Claim claim) {
+ return claim.equals(selectedItem);
+ }
+
+ 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.GONE : View.VISIBLE);
+ vh.allTextView.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().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..ed87c566
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java
@@ -0,0 +1,217 @@
+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.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.utils.Helper;
+import io.lbry.browser.utils.LbryUri;
+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 Context context;
+ private List items;
+ private List selectedItems;
+ @Setter
+ private ClaimListItemListener listener;
+
+ public ClaimListAdapter(List items, Context context) {
+ this.context = context;
+ this.items = new ArrayList<>(items);
+ this.selectedItems = new ArrayList<>();
+ }
+
+ public List getSelectedItems() {
+ return this.selectedItems;
+ }
+ 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 clearItems() {
+ clearSelectedItems();
+ this.items.clear();
+ notifyDataSetChanged();
+ }
+
+ 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);
+ }
+ }
+ notifyDataSetChanged();
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ protected ImageView thumbnailView;
+ protected View noThumbnailView;
+ protected TextView alphaView;
+ protected TextView vanityUrlView;
+ protected TextView durationView;
+ protected TextView titleView;
+ protected TextView publisherView;
+ protected TextView publishTimeView;
+ public ViewHolder(View v) {
+ super(v);
+ 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);
+ }
+ }
+
+ @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;
+ }
+
+ return Claim.TYPE_CHANNEL.equalsIgnoreCase(items.get(position).getValueType()) ? VIEW_TYPE_CHANNEL : VIEW_TYPE_STREAM;
+ }
+
+ @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);
+ }
+
+ @Override
+ public void onBindViewHolder(ClaimListAdapter.ViewHolder vh, int position) {
+ int type = getItemViewType(position);
+ Claim item = items.get(position);
+ 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());
+ }
+
+ vh.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (listener != null) {
+ listener.onClaimClicked(item);
+ }
+ }
+ });
+
+ vh.publisherView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (listener != null && signingChannel != null) {
+ listener.onClaimClicked(signingChannel);
+ }
+ }
+ });
+
+
+ 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.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.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));
+ } 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));
+ }
+ }
+ }
+
+ public interface ClaimListItemListener {
+ void onClaimClicked(Claim claim);
+ }
+}
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..2f1de9be
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/adapter/NavigationMenuAdapter.java
@@ -0,0 +1,105 @@
+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 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 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);
+ vh.titleView.setText(item.getTitle());
+ 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/SuggestedChannelGridAdapter.java b/app/src/main/java/io/lbry/browser/adapter/SuggestedChannelGridAdapter.java
new file mode 100644
index 00000000..5a68f561
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/adapter/SuggestedChannelGridAdapter.java
@@ -0,0 +1,120 @@
+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 ImageView thumbnailView;
+ protected TextView alphaView;
+ protected TextView titleView;
+ protected TextView tagView;
+ public ViewHolder(View v) {
+ super(v);
+ 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 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();
+ if (!Helper.isNullOrEmpty(thumbnailUrl)) {
+ vh.alphaView.setVisibility(View.GONE);
+ vh.thumbnailView.setVisibility(View.VISIBLE);
+ Glide.with(context.getApplicationContext()).load(thumbnailUrl).apply(RequestOptions.circleCropTransform()).into(vh.thumbnailView);
+ } else {
+ vh.alphaView.setVisibility(View.VISIBLE);
+ vh.thumbnailView.setVisibility(View.GONE);
+ }
+
+ vh.alphaView.setText(claim.getFirstCharacter());
+ 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..5a7a4faa
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/adapter/TagListAdapter.java
@@ -0,0 +1,74 @@
+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.model.Tag;
+import lombok.Setter;
+
+public class TagListAdapter extends RecyclerView.Adapter {
+ private Context context;
+ private List items;
+ @Setter
+ private TagClickListener clickListener;
+
+ public TagListAdapter(List tags, Context context) {
+ this.context = context;
+ this.items = new ArrayList<>(tags);
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ protected TextView nameView;
+ public ViewHolder(View v) {
+ super(v);
+ nameView = v.findViewById(R.id.tag_name);
+ }
+ }
+
+ public int getItemCount() {
+ return items != null ? items.size() : 0;
+ }
+
+ public void addTags(List tags) {
+ for (Tag tag : tags) {
+ if (!items.contains(tag)) {
+ items.add(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.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);
+ }
+ }
+ });
+ }
+
+ public interface TagClickListener {
+ void onTagClicked(Tag tag);
+ }
+}
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..ff4bd478
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/adapter/TransactionListAdapter.java
@@ -0,0 +1,126 @@
+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.ImageView;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import io.lbry.browser.R;
+import io.lbry.browser.model.Claim;
+import io.lbry.browser.model.Transaction;
+import io.lbry.browser.model.UrlSuggestion;
+import io.lbry.browser.utils.Helper;
+import io.lbry.browser.utils.LbryUri;
+import lombok.Setter;
+
+public class TransactionListAdapter extends RecyclerView.Adapter {
+
+ private static final String EXPLORER_TX_PREFIX = "https://explorer.lbry.com/tx";
+ 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, 8));
+ 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.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", 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);
+ }
+}
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..4b164be9
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java
@@ -0,0 +1,138 @@
+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.model.Claim;
+import io.lbry.browser.model.UrlSuggestion;
+import io.lbry.browser.ui.controls.SolidIconView;
+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();
+ if (thisUrl != null && thisUrl.equals(url)) {
+ items.get(i).setClaim(claim);
+ }
+ }
+ }
+
+ 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() :
+ 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() :
+ 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..3591654f
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/adapter/VerificationPagerAdapter.java
@@ -0,0 +1,52 @@
+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.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 {
+ 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:
+ WalletVerificationFragment wvFragment = WalletVerificationFragment.class.newInstance();
+ if (activity instanceof WalletSyncListener) {
+ wvFragment.setListener((WalletSyncListener) activity);
+ }
+ return wvFragment;
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return 2;
+ }
+}
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..cca1c4d0
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/data/DatabaseHelper.java
@@ -0,0 +1,74 @@
+package io.lbry.browser.data;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.lbry.browser.model.lbryinc.Subscription;
+import io.lbry.browser.utils.Helper;
+
+public class DatabaseHelper extends SQLiteOpenHelper {
+ public static final int DATABASE_VERSION = 1;
+ public static final String DATABASE_NAME = "LbryApp.db";
+
+ private static final String[] SQL_CREATE_TABLES = {
+ // local subscription store
+ "CREATE TABLE subscriptions (url TEXT PRIMARY KEY NOT NULL, channel_name TEXT NOT NULL)",
+
+ // local claim cache store for quick load / refresh
+
+ };
+ private static final String[] SQL_CREATE_INDEXES = {
+ "CREATE UNIQUE INDEX idx_subscription_url ON subscriptions (url)"
+ };
+
+ 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";
+
+ public DatabaseHelper(Context context) {
+ super(context, String.format("%s/%s", context.getFilesDir().getAbsolutePath(), DATABASE_NAME), null, DATABASE_VERSION);
+ }
+ 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) {
+
+ }
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+
+ }
+
+ 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/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/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/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/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/SignInListener.java b/app/src/main/java/io/lbry/browser/listener/SignInListener.java
new file mode 100644
index 00000000..0217a91f
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/listener/SignInListener.java
@@ -0,0 +1,7 @@
+package io.lbry.browser.listener;
+
+public interface SignInListener {
+ void onEmailAdded(String email);
+ void onEmailEdit();
+ void onEmailVerified();
+}
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..549a656d
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/Claim.java
@@ -0,0 +1,338 @@
+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.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 lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+
+@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 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 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
+ private Claim signingChannel;
+ private String repostChannelUrl;
+ private boolean isChannelSignatureValid;
+ private GenericMetadata value;
+ private File file; // associated file if it exists
+
+ 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 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 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 fromJSONObject(JSONObject claimObject) {
+ 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 claim = gson.fromJson(claimJson, type);
+
+ // Specific value parsing
+ try {
+ JSONObject value = claimObject.getJSONObject("value");
+ String valueType = claim.getValueType();
+ 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"));
+
+ 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(feeAmount));
+ }
+
+ ((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"));
+
+ claimUri.setChannelClaimId(signingChannel.getClaimId());
+ claimUri.setChannelName(signingChannel.getName());
+
+ 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..da9792be
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/ClaimCacheKey.java
@@ -0,0 +1,86 @@
+package io.lbry.browser.model;
+
+import androidx.annotation.Nullable;
+
+import io.lbry.browser.utils.Helper;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Class to represent a key to check equality with another object
+ */
+public class ClaimCacheKey {
+ @Getter
+ @Setter
+ private String claimId;
+ @Getter
+ @Setter
+ private String canonicalUrl;
+ @Getter
+ @Setter
+ private String permanentUrl;
+ @Getter
+ @Setter
+ private String shortUrl;
+ @Getter
+ @Setter
+ private String vanityUrl;
+
+ public static ClaimCacheKey fromClaim(Claim claim) {
+ ClaimCacheKey key = new ClaimCacheKey();
+ key.setClaimId(claim.getClaimId());
+ key.setCanonicalUrl(claim.getCanonicalUrl());
+ key.setPermanentUrl(claim.getPermanentUrl());
+ key.setShortUrl(claim.getShortUrl());
+
+ 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(permanentUrl) && !Helper.isNullOrEmpty(key.getPermanentUrl())) {
+ return permanentUrl.equalsIgnoreCase(key.getPermanentUrl());
+ }
+ if (!Helper.isNullOrEmpty(canonicalUrl) && !Helper.isNullOrEmpty(key.getCanonicalUrl())) {
+ return canonicalUrl.equalsIgnoreCase(key.getCanonicalUrl());
+ }
+ if (!Helper.isNullOrEmpty(shortUrl) && !Helper.isNullOrEmpty(key.getShortUrl())) {
+ return shortUrl.equalsIgnoreCase(key.getShortUrl());
+ }
+ if (!Helper.isNullOrEmpty(vanityUrl) && !Helper.isNullOrEmpty(key.getVanityUrl())) {
+ return vanityUrl.equalsIgnoreCase(key.getVanityUrl());
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ if (!Helper.isNullOrEmpty(claimId)) {
+ return claimId.hashCode();
+ }
+ if (!Helper.isNullOrEmpty(permanentUrl)) {
+ return permanentUrl.hashCode();
+ }
+ if (!Helper.isNullOrEmpty(canonicalUrl)) {
+ return canonicalUrl.hashCode();
+ }
+ if (!Helper.isNullOrEmpty(shortUrl)) {
+ return shortUrl.hashCode();
+ }
+ if (!Helper.isNullOrEmpty(vanityUrl)) {
+ return vanityUrl.hashCode();
+ }
+
+ return super.hashCode();
+ }
+}
+
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/File.java b/app/src/main/java/io/lbry/browser/model/File.java
new file mode 100644
index 00000000..ca68e86a
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/File.java
@@ -0,0 +1,54 @@
+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 lombok.Data;
+
+@Data
+public class File {
+ private Claim.StreamMetadata metadata;
+ private long addedOn;
+ private int blobsCompleted;
+ private int blobsInStream;
+ private int blobsRemaining;
+ private String channelClaimId;
+ private String channelName;
+ 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 totalBytes;
+ private long totalBytesLowerBound;
+ private String txid;
+ private long writtenBytes;
+
+ public static File fromJSONObject(JSONObject fileObject) {
+ String fileJson = fileObject.toString();
+ Type type = new TypeToken(){}.getType();
+ Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+ File file = gson.fromJson(fileJson, type);
+ return file;
+ }
+}
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..02e4dd14
--- /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_YOUR_TAGS = 103;
+ public static final int ID_ITEM_ALL_CONTENT = 104;
+
+ // 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 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..c89d3691
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/Tag.java
@@ -0,0 +1,30 @@
+package io.lbry.browser.model;
+
+import io.lbry.browser.utils.Helper;
+import lombok.Getter;
+import lombok.Setter;
+
+public class Tag {
+ @Getter
+ @Setter
+ private String name;
+
+ public Tag(String name) {
+ this.name = name;
+ }
+
+ public String getLowercaseName() {
+ return name.toLowerCase();
+ }
+
+ public boolean isMature() {
+ return Helper.MATURE_TAG_NAMES.contains(name.toLowerCase());
+ }
+
+ public boolean equals(Object o) {
+ return (o instanceof Tag) && ((Tag) o).getName().equalsIgnoreCase(name);
+ }
+ public int hashCode() {
+ return name.hashCode();
+ }
+}
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..deae9759
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/Transaction.java
@@ -0,0 +1,92 @@
+package io.lbry.browser.model;
+
+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.utils.Helper;
+import lombok.Data;
+
+@Data
+public class Transaction {
+ private int confirmations;
+ private Date txDate;
+ private String date;
+ private String claim;
+ private String txid;
+ private BigDecimal value;
+ private BigDecimal fee;
+ private long timestamp;
+ private int descriptionStringId;
+ private TransactionInfo abandonInfo;
+ private TransactionInfo claimInfo;
+ private TransactionInfo supportInfo;
+
+ 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")) {
+ info = TransactionInfo.fromJSONObject(jsonObject.getJSONObject("abandon_info"));
+ descStringId = R.string.abandon;
+ transaction.setAbandonInfo(info);
+ } else if (jsonObject.has("claim_info")) {
+ info = TransactionInfo.fromJSONObject(jsonObject.getJSONObject("claim_info"));
+ descStringId = info.getClaimName().startsWith("@") ? R.string.channel : R.string.publish;
+ transaction.setClaimInfo(info);
+ } else if (jsonObject.has("support_info")) {
+ info = TransactionInfo.fromJSONObject(jsonObject.getJSONObject("support_info"));
+ descStringId = info.isTip() ? R.string.tip : R.string.support;
+ transaction.setSupportInfo(info);
+ }
+
+ if (info != null) {
+ transaction.setClaim(info.getClaimName());
+ }
+ } catch (JSONException ex) {
+ // pass
+ }
+
+ 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 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.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..f0685329
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/UrlSuggestion.java
@@ -0,0 +1,35 @@
+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
+
+ public UrlSuggestion(int type, String text) {
+ this.type = type;
+ this.text = text;
+ }
+
+ public String getTitle() {
+ switch (type) {
+ case TYPE_CHANNEL:
+ return String.format("%s - %s", text.startsWith("@") ? text.substring(1) : text, uri.toString());
+ case TYPE_FILE:
+ return String.format("%s - %s", text, uri.toString());
+ case TYPE_TAG:
+ return String.format("%s - #%s", text, text);
+ }
+
+ return text;
+ }
+}
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..c9ab5fab
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/WalletSync.java
@@ -0,0 +1,16 @@
+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, boolean changed) {
+ this.hash = hash;
+ this.data = data;
+ this.changed = changed;
+ }
+}
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..f2bd817f
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/lbryinc/Subscription.java
@@ -0,0 +1,17 @@
+package io.lbry.browser.model.lbryinc;
+
+import lombok.Data;
+
+@Data
+public class Subscription {
+ private String channelName;
+ private String url;
+
+ public Subscription() {
+
+ }
+ public Subscription(String channelName, String url) {
+ this.channelName = channelName;
+ this.url = url;
+ }
+}
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..102ec87e
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/lbryinc/User.java
@@ -0,0 +1,29 @@
+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;
+}
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