initial native rewrite commit
This commit is contained in:
parent
cc3055f1c9
commit
cf052f9c80
639 changed files with 14299 additions and 8391 deletions
33
Dockerfile
33
Dockerfile
|
@ -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"]
|
|
1
app/.gitignore
vendored
Normal file
1
app/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
55
app/BUCK
55
app/BUCK
|
@ -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",
|
|
||||||
],
|
|
||||||
)
|
|
238
app/build.gradle
238
app/build.gradle
|
@ -1,137 +1,8 @@
|
||||||
apply plugin: "com.android.application"
|
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);
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
compileSdkVersion 29
|
||||||
|
buildToolsVersion "29.0.1"
|
||||||
flavorDimensions "default"
|
flavorDimensions "default"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
@ -141,18 +12,14 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "io.lbry.browser"
|
applicationId "io.lbry.browser"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion 21
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion 29
|
||||||
versionCode 1402
|
versionCode 1500
|
||||||
versionName "0.14.2"
|
versionName "0.15.0"
|
||||||
missingDimensionStrategy 'react-native-camera', 'general'
|
|
||||||
multiDexEnabled true
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
|
||||||
dexOptions {
|
|
||||||
javaMaxHeapSize "2048M"
|
|
||||||
preDexLibraries false
|
|
||||||
jumboMode true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
__32bit {
|
__32bit {
|
||||||
versionCode android.defaultConfig.versionCode * 10 + 1
|
versionCode android.defaultConfig.versionCode * 10 + 1
|
||||||
|
@ -167,66 +34,53 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
signingConfigs {
|
|
||||||
debug {
|
|
||||||
storeFile file('debug.keystore')
|
|
||||||
storePassword 'android'
|
|
||||||
keyAlias 'androiddebugkey'
|
|
||||||
keyPassword 'android'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
|
||||||
signingConfig signingConfigs.debug
|
|
||||||
}
|
|
||||||
release {
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':@react-native-community_async-storage')
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation project(':react-native-camera')
|
|
||||||
implementation project(':react-native-exception-handler')
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
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 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.media:media:1.0.0'
|
implementation 'com.google.android.material:material:1.1.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
implementation "androidx.cardview:cardview:1.0.0"
|
||||||
implementation 'com.facebook.react:react-native:0.61.5'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'com.facebook.fresco:fresco:1.9.0'
|
implementation 'androidx.navigation:navigation-fragment:2.2.2'
|
||||||
implementation 'com.facebook.fresco:animated-gif:1.9.0'
|
implementation 'androidx.navigation:navigation-ui:2.2.2'
|
||||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||||
implementation 'com.google.firebase:firebase-analytics:17.2.1'
|
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
||||||
implementation 'com.google.android.gms:play-services-base:17.1.0'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
implementation 'com.facebook.fresco:animated-base-support:1.3.0'
|
implementation 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||||
implementation 'com.facebook.fresco:animated-gif:1.10.0'
|
implementation 'com.google.firebase:firebase-analytics:17.4.0'
|
||||||
implementation 'com.google.firebase:firebase-messaging:20.1.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')
|
__32bitImplementation files('libs/lbrysdk-0.67.1-release__arm.aar')
|
||||||
__64bitImplementation files('libs/lbrysdk-0.67.1-release__arm64.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'
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
|
|
@ -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,
|
|
||||||
)
|
|
Binary file not shown.
21
app/proguard-rules.pro
vendored
21
app/proguard-rules.pro
vendored
|
@ -1,10 +1,21 @@
|
||||||
# Add project specific ProGuard rules here.
|
# Add project specific ProGuard rules here.
|
||||||
# By default, the flags in this file are appended to flags specified
|
# You can control the set of applied configuration files using the
|
||||||
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
|
# proguardFiles setting in build.gradle.
|
||||||
# You can edit the include path and order by changing the proguardFiles
|
|
||||||
# directive in build.gradle.
|
|
||||||
#
|
#
|
||||||
# For more details, see
|
# For more details, see
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
# 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
|
||||||
|
|
|
@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="io.lbry.browser"
|
package="io.lbry.browser"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
|
|
||||||
<supports-screens
|
|
||||||
android:smallScreens="true"
|
|
||||||
android:normalScreens="true"
|
|
||||||
android:largeScreens="true"
|
|
||||||
android:anyDensity="true"
|
|
||||||
android:xlargeScreens="true"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
@ -18,68 +10,48 @@
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application android:label="@string/app_name"
|
<application
|
||||||
android:icon="@drawable/icon"
|
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:theme="@style/LbryAppTheme"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:hardwareAccelerated="true"
|
|
||||||
android:usesCleartextTraffic="true">
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
|
||||||
android:resource="@drawable/ic_lbry" />
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.firebase.messaging.default_notification_color"
|
|
||||||
android:resource="@color/lbryGreen" />
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
|
||||||
android:value="@string/default_notification_channel_id"/>
|
|
||||||
|
|
||||||
<meta-data android:name="wakelock" android:value="0"/>
|
|
||||||
|
|
||||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
|
||||||
|
|
||||||
<activity android:name="io.lbry.browser.MainActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/LbryAppTheme"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:screenOrientation="portrait"
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
|
<activity
|
||||||
|
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout"
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:supportsPictureInPicture="true"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
>
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<data android:scheme="lbry" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<receiver android:name="io.lbry.browser.receivers.NotificationDeletedReceiver" />
|
<activity
|
||||||
|
android:name=".FirstRunActivity"
|
||||||
<service
|
android:launchMode="singleInstance"
|
||||||
android:name="io.lbry.browser.LbrynetMessagingService"
|
android:parentActivityName=".MainActivity"
|
||||||
android:exported="false">
|
android:theme="@style/AppTheme.NoActionBarTranslucent" />
|
||||||
<intent-filter>
|
<activity
|
||||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
android:name=".VerificationActivity"
|
||||||
</intent-filter>
|
android:launchMode="singleInstance"
|
||||||
</service>
|
android:parentActivityName=".MainActivity"
|
||||||
|
android:theme="@style/AppTheme.NoActionBarTranslucent"
|
||||||
<provider
|
android:windowSoftInputMode="adjustResize" />
|
||||||
android:name="io.lbry.browser.LocalFileProvider"
|
<activity
|
||||||
android:authorities="io.lbry.browser.fileprovider"
|
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout"
|
||||||
android:grantUriPermissions="true"
|
android:name=".FileViewActivity"
|
||||||
android:exported="false">
|
android:launchMode="singleInstance"
|
||||||
<meta-data
|
android:parentActivityName=".MainActivity"
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:supportsPictureInPicture="true"
|
||||||
android:resource="@xml/filepaths" />
|
android:theme="@style/AppTheme.NoActionBarBlack" />
|
||||||
</provider>
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
BIN
app/src/main/assets/font_awesome_5_free_solid.otf
Normal file
BIN
app/src/main/assets/font_awesome_5_free_solid.otf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/src/main/ic_launcher-playstore.png
Normal file
BIN
app/src/main/ic_launcher-playstore.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
|
@ -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<String> activeDownloads = new ArrayList<String>();
|
|
||||||
|
|
||||||
private List<String> completedDownloads = new ArrayList<String>();
|
|
||||||
|
|
||||||
private Map<String, String> downloadIdOutpointsMap = new HashMap<String, String>();
|
|
||||||
|
|
||||||
// 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<String, Double> writtenDownloadBytes = new HashMap<String, Double>();
|
|
||||||
|
|
||||||
private HashMap<Integer, NotificationCompat.Builder> builders = new HashMap<Integer, NotificationCompat.Builder>();
|
|
||||||
|
|
||||||
private HashMap<String, Integer> downloadIdNotificationIdMap = new HashMap<String, Integer>();
|
|
||||||
|
|
||||||
private HashMap<String, Boolean> stoppedDownloadsMap = new HashMap<String, Boolean>();
|
|
||||||
|
|
||||||
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<String> getActiveDownloads() {
|
|
||||||
return activeDownloads;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> 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);
|
|
||||||
}
|
|
||||||
}
|
|
468
app/src/main/java/io/lbry/browser/FileViewActivity.java
Normal file
468
app/src/main/java/io/lbry/browser/FileViewActivity.java
Normal file
|
@ -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<File> 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<Claim> 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<Tag> 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<Claim> claims, boolean hasReachedEnd) {
|
||||||
|
List<Claim> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
app/src/main/java/io/lbry/browser/FirstRunActivity.java
Normal file
128
app/src/main/java/io/lbry/browser/FirstRunActivity.java
Normal file
|
@ -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<Void, Void, Void> {
|
||||||
|
private Context context;
|
||||||
|
public AuthenticateTask(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
Lbryio.authenticate(context);
|
||||||
|
return 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<String, String> 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<String> getEnabledTypes() {
|
|
||||||
SharedPreferences sp = getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
|
||||||
List<String> enabledTypes = new ArrayList<String>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package io.lbry.browser;
|
|
||||||
|
|
||||||
import androidx.core.content.FileProvider;
|
|
||||||
|
|
||||||
public class LocalFileProvider extends FileProvider {
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
152
app/src/main/java/io/lbry/browser/VerificationActivity.java
Normal file
152
app/src/main/java/io/lbry/browser/VerificationActivity.java
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<ChannelFilterListAdapter.ViewHolder> {
|
||||||
|
private Context context;
|
||||||
|
private List<Claim> 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<Claim> 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
217
app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java
Normal file
217
app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java
Normal file
|
@ -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<ClaimListAdapter.ViewHolder> {
|
||||||
|
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<Claim> items;
|
||||||
|
private List<Claim> selectedItems;
|
||||||
|
@Setter
|
||||||
|
private ClaimListItemListener listener;
|
||||||
|
|
||||||
|
public ClaimListAdapter(List<Claim> items, Context context) {
|
||||||
|
this.context = context;
|
||||||
|
this.items = new ArrayList<>(items);
|
||||||
|
this.selectedItems = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Claim> 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<Claim> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<NavigationMenuAdapter.ViewHolder> {
|
||||||
|
private static final int TYPE_GROUP = 1;
|
||||||
|
private static final int TYPE_ITEM = 2;
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private List<NavMenuItem> menuItems;
|
||||||
|
private NavMenuItem currentItem;
|
||||||
|
@Setter
|
||||||
|
private NavigationMenuItemClickListener listener;
|
||||||
|
|
||||||
|
public NavigationMenuAdapter(List<NavMenuItem> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<SuggestedChannelGridAdapter.ViewHolder> {
|
||||||
|
private Context context;
|
||||||
|
private List<Claim> items;
|
||||||
|
private List<Claim> selectedItems;
|
||||||
|
@Setter
|
||||||
|
private ChannelItemSelectionListener listener;
|
||||||
|
|
||||||
|
public SuggestedChannelGridAdapter(List<Claim> 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<Claim> getSelectedItems() {
|
||||||
|
return this.selectedItems;
|
||||||
|
}
|
||||||
|
public void clearSelectedItems() {
|
||||||
|
this.selectedItems.clear();
|
||||||
|
}
|
||||||
|
public boolean isClaimSelected(Claim claim) {
|
||||||
|
return selectedItems.contains(claim);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addClaims(List<Claim> 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<TagListAdapter.ViewHolder> {
|
||||||
|
private Context context;
|
||||||
|
private List<Tag> items;
|
||||||
|
@Setter
|
||||||
|
private TagClickListener clickListener;
|
||||||
|
|
||||||
|
public TagListAdapter(List<Tag> 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<Tag> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<TransactionListAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
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<Transaction> items;
|
||||||
|
@Setter
|
||||||
|
private TransactionClickListener listener;
|
||||||
|
|
||||||
|
public TransactionListAdapter(List<Transaction> transactions, Context context) {
|
||||||
|
this.context = context;
|
||||||
|
this.items = new ArrayList<>(transactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
items.clear();
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Transaction> getItems() {
|
||||||
|
return new ArrayList<>(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTransactions(List<Transaction> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<UrlSuggestionListAdapter.ViewHolder> {
|
||||||
|
private Context context;
|
||||||
|
private List<UrlSuggestion> items;
|
||||||
|
@Setter
|
||||||
|
private UrlSuggestionClickListener listener;
|
||||||
|
|
||||||
|
public UrlSuggestionListAdapter(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
this.items = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
items.clear();
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UrlSuggestion> getItems() {
|
||||||
|
return new ArrayList<>(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getItemUrls() {
|
||||||
|
List<String> 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<UrlSuggestion> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
74
app/src/main/java/io/lbry/browser/data/DatabaseHelper.java
Normal file
74
app/src/main/java/io/lbry/browser/data/DatabaseHelper.java
Normal file
|
@ -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<Subscription> getSubscriptions(SQLiteDatabase db) {
|
||||||
|
List<Subscription> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package io.lbry.browser.listener;
|
||||||
|
|
||||||
|
public interface SdkStatusListener {
|
||||||
|
void onSdkReady();
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package io.lbry.browser.listener;
|
||||||
|
|
||||||
|
public interface SignInListener {
|
||||||
|
void onEmailAdded(String email);
|
||||||
|
void onEmailEdit();
|
||||||
|
void onEmailVerified();
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package io.lbry.browser.listener;
|
||||||
|
|
||||||
|
import io.lbry.browser.model.WalletBalance;
|
||||||
|
|
||||||
|
public interface WalletBalanceListener {
|
||||||
|
void onWalletBalanceUpdated(WalletBalance walletBalance);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package io.lbry.browser.listener;
|
||||||
|
|
||||||
|
public interface WalletSyncListener {
|
||||||
|
void onWalletSyncProcessing();
|
||||||
|
void onWalletSyncWaitingForInput();
|
||||||
|
void onWalletSyncEnabled();
|
||||||
|
void onWalletSyncFailed(Exception error);
|
||||||
|
}
|
338
app/src/main/java/io/lbry/browser/model/Claim.java
Normal file
338
app/src/main/java/io/lbry/browser/model/Claim.java
Normal file
|
@ -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<String> CLAIM_TYPES = Arrays.asList(TYPE_CHANNEL, TYPE_STREAM);
|
||||||
|
public static final List<String> 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<String> getTags() {
|
||||||
|
return (value != null && value.getTags() != null) ? new ArrayList<>(value.getTags()) : new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Tag> getTagObjects() {
|
||||||
|
List<Tag> 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<Claim>(){}.getType();
|
||||||
|
Type streamMetadataType = new TypeToken<StreamMetadata>(){}.getType();
|
||||||
|
Type channelMetadataType = new TypeToken<ChannelMetadata>(){}.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<String> languages;
|
||||||
|
private List<String> tags;
|
||||||
|
private List<Location> 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<String> 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
|
||||||
|
}
|
||||||
|
}
|
86
app/src/main/java/io/lbry/browser/model/ClaimCacheKey.java
Normal file
86
app/src/main/java/io/lbry/browser/model/ClaimCacheKey.java
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
10
app/src/main/java/io/lbry/browser/model/Fee.java
Normal file
10
app/src/main/java/io/lbry/browser/model/Fee.java
Normal file
|
@ -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;
|
||||||
|
}
|
54
app/src/main/java/io/lbry/browser/model/File.java
Normal file
54
app/src/main/java/io/lbry/browser/model/File.java
Normal file
|
@ -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<File>(){}.getType();
|
||||||
|
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
|
||||||
|
File file = gson.fromJson(fileJson, type);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
13
app/src/main/java/io/lbry/browser/model/Location.java
Normal file
13
app/src/main/java/io/lbry/browser/model/Location.java
Normal file
|
@ -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;
|
||||||
|
}
|
68
app/src/main/java/io/lbry/browser/model/NavMenuItem.java
Normal file
68
app/src/main/java/io/lbry/browser/model/NavMenuItem.java
Normal file
|
@ -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<NavMenuItem> 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;
|
||||||
|
}
|
||||||
|
}
|
30
app/src/main/java/io/lbry/browser/model/Tag.java
Normal file
30
app/src/main/java/io/lbry/browser/model/Tag.java
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
92
app/src/main/java/io/lbry/browser/model/Transaction.java
Normal file
92
app/src/main/java/io/lbry/browser/model/Transaction.java
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
app/src/main/java/io/lbry/browser/model/UrlSuggestion.java
Normal file
35
app/src/main/java/io/lbry/browser/model/UrlSuggestion.java
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
24
app/src/main/java/io/lbry/browser/model/WalletBalance.java
Normal file
24
app/src/main/java/io/lbry/browser/model/WalletBalance.java
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
16
app/src/main/java/io/lbry/browser/model/WalletSync.java
Normal file
16
app/src/main/java/io/lbry/browser/model/WalletSync.java
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
29
app/src/main/java/io/lbry/browser/model/lbryinc/User.java
Normal file
29
app/src/main/java/io/lbry/browser/model/lbryinc/User.java
Normal file
|
@ -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<String> 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<String> youtubeChannels;
|
||||||
|
private List<String> deviceTypes;
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<String, Object> payloadMap = payload.toHashMap();
|
|
||||||
for (Map.Entry<String, Object> 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<InstanceIdResult>() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(Task<InstanceIdResult> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<GalleryItem> 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<Void, Void, String>() {
|
|
||||||
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<Void, Void, String>() {
|
|
||||||
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<GalleryItem> loadVideos() {
|
|
||||||
String[] projection = {
|
|
||||||
MediaStore.MediaColumns._ID,
|
|
||||||
MediaStore.MediaColumns.DATA,
|
|
||||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
|
||||||
MediaStore.MediaColumns.MIME_TYPE,
|
|
||||||
MediaStore.Video.Media.DURATION
|
|
||||||
};
|
|
||||||
|
|
||||||
List<String> ids = new ArrayList<String>();
|
|
||||||
List<GalleryItem> items = new ArrayList<GalleryItem>();
|
|
||||||
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<String> ids) {
|
|
||||||
(new AsyncTask<Void, Void, Void>() {
|
|
||||||
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()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Void, Void, String>() {
|
|
||||||
@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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<ReadableMap> queue;
|
|
||||||
|
|
||||||
private ReadableMap filter;
|
|
||||||
|
|
||||||
private ReadableMap lastState;
|
|
||||||
|
|
||||||
private AsyncTask persistTask;
|
|
||||||
|
|
||||||
public StatePersistorModule(ReactApplicationContext reactContext) {
|
|
||||||
super(reactContext);
|
|
||||||
this.context = reactContext;
|
|
||||||
queue = new ArrayList<ReadableMap>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@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<Object, Void, Boolean>() {
|
|
||||||
protected Boolean doInBackground(Object... param) {
|
|
||||||
// get the first item in the queue
|
|
||||||
ReadableMap queuedState = null;
|
|
||||||
if (queue.size() > 0) {
|
|
||||||
synchronized (StatePersistorModule.this) {
|
|
||||||
queuedState = queue.remove(flush ? queue.size() - 1 : 0);
|
|
||||||
if (flush) {
|
|
||||||
// we only want the final state in this scenario
|
|
||||||
queue.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queuedState != null) {
|
|
||||||
ReadableMap state = queuedState; //(ReadableMap) filterState(queuedState);
|
|
||||||
// convert to JSON object
|
|
||||||
|
|
||||||
try {
|
|
||||||
JSONObject json = readableMapToJSON(state);
|
|
||||||
|
|
||||||
// save the state file
|
|
||||||
// TODO: explore this option at a later date
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
} catch (JSONException ex) {
|
|
||||||
// normally shouldn't happen, but if it does, reinsert into the queue
|
|
||||||
if (queuedState != null) {
|
|
||||||
synchronized (StatePersistorModule.this) {
|
|
||||||
queue.add(0, queuedState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPostExecute(Boolean result) {
|
|
||||||
if (queue.size() > 0) {
|
|
||||||
persistState();
|
|
||||||
}
|
|
||||||
|
|
||||||
persistTask = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
persistTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void flush() {
|
|
||||||
persistState(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JSONObject readableMapToJSON(ReadableMap readableMap) throws JSONException {
|
|
||||||
JSONObject json = new JSONObject();
|
|
||||||
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
|
|
||||||
while (iterator.hasNextKey()) {
|
|
||||||
String key = iterator.nextKey();
|
|
||||||
switch (readableMap.getType(key)) {
|
|
||||||
case Map:
|
|
||||||
json.put(key, readableMapToJSON(readableMap.getMap(key)));
|
|
||||||
break;
|
|
||||||
case Array:
|
|
||||||
json.put(key, readableArrayToJSON(readableMap.getArray(key)));
|
|
||||||
break;
|
|
||||||
case Boolean:
|
|
||||||
json.put(key, readableMap.getBoolean(key));
|
|
||||||
break;
|
|
||||||
case Null:
|
|
||||||
json.put(key, JSONObject.NULL);
|
|
||||||
break;
|
|
||||||
case Number:
|
|
||||||
json.put(key, readableMap.getDouble(key));
|
|
||||||
break;
|
|
||||||
case String:
|
|
||||||
json.put(key, readableMap.getString(key));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JSONArray readableArrayToJSON(ReadableArray readableArray) throws JSONException {
|
|
||||||
JSONArray array = new JSONArray();
|
|
||||||
for (int i = 0; i < readableArray.size(); i++) {
|
|
||||||
switch (readableArray.getType(i)) {
|
|
||||||
case Null:
|
|
||||||
break;
|
|
||||||
case Boolean:
|
|
||||||
array.put(readableArray.getBoolean(i));
|
|
||||||
break;
|
|
||||||
case Number:
|
|
||||||
array.put(readableArray.getDouble(i));
|
|
||||||
break;
|
|
||||||
case String:
|
|
||||||
array.put(readableArray.getString(i));
|
|
||||||
break;
|
|
||||||
case Map:
|
|
||||||
array.put(readableMapToJSON(readableArray.getMap(i)));
|
|
||||||
break;
|
|
||||||
case Array:
|
|
||||||
array.put(readableArrayToJSON(readableArray.getArray(i)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,537 +0,0 @@
|
||||||
package io.lbry.browser.reactmodules;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.Manifest;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Build;
|
|
||||||
import androidx.core.content.FileProvider;
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import android.telephony.TelephonyManager;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
|
|
||||||
import com.facebook.react.bridge.Callback;
|
|
||||||
import com.facebook.react.bridge.Promise;
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
|
||||||
import com.facebook.react.bridge.ReactContext;
|
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
||||||
import com.facebook.react.bridge.ReactMethod;
|
|
||||||
import com.facebook.react.common.MapBuilder;
|
|
||||||
|
|
||||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
||||||
import com.squareup.picasso.Picasso;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
|
|
||||||
import io.lbry.browser.DownloadManager;
|
|
||||||
import io.lbry.browser.MainActivity;
|
|
||||||
import io.lbry.browser.R;
|
|
||||||
import io.lbry.lbrysdk.LbrynetService;
|
|
||||||
import io.lbry.lbrysdk.Utils;
|
|
||||||
|
|
||||||
public class UtilityModule extends ReactContextBaseJavaModule {
|
|
||||||
private static final Map<String, Integer> activeNotifications = new HashMap<String, Integer>();
|
|
||||||
|
|
||||||
private static final String FILE_PROVIDER = "io.lbry.browser.fileprovider";
|
|
||||||
|
|
||||||
private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.SUBSCRIPTIONS_NOTIFICATION_CHANNEL";
|
|
||||||
|
|
||||||
public static final String ACTION_NOTIFICATION_PLAY = "io.lbry.browser.ACTION_NOTIFICATION_PLAY";
|
|
||||||
|
|
||||||
public static final String ACTION_NOTIFICATION_LATER = "io.lbry.browser.ACTION_NOTIFICATION_LATER";
|
|
||||||
|
|
||||||
// Setting keys from React Native
|
|
||||||
public static final String RECEIVE_SUBSCRIPTION_NOTIFICATIONS = "receiveSubscriptionNotifications";
|
|
||||||
|
|
||||||
public static final String RECEIVE_REWARD_NOTIFICATIONS = "receiveRewardNotifications";
|
|
||||||
|
|
||||||
public static final String RECEIVE_INTERESTS_NOTIFICATIONS = "receiveInterestsNotifications";
|
|
||||||
|
|
||||||
public static final String RECEIVE_CREATOR_NOTIFICATIONS = "receiveCreatorNotifications";
|
|
||||||
|
|
||||||
public static final String DHT_ENABLED = "dhtEnabled";
|
|
||||||
|
|
||||||
// the last language set to be loaded
|
|
||||||
private static final String LANGUAGE_SETTING_KEY = "language";
|
|
||||||
|
|
||||||
private String language;
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
private KeyStore keyStore;
|
|
||||||
|
|
||||||
public UtilityModule(ReactApplicationContext reactContext) {
|
|
||||||
super(reactContext);
|
|
||||||
this.context = reactContext;
|
|
||||||
try {
|
|
||||||
this.keyStore = Utils.initKeyStore(context);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
// continue without keystore
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
|
||||||
language = sp.getString(LANGUAGE_SETTING_KEY, "en");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getConstants() {
|
|
||||||
final Map<String, Object> constants = MapBuilder.newHashMap();
|
|
||||||
constants.put("language", language);
|
|
||||||
constants.put("dhtEnabled", LbrynetService.isDHTEnabled());
|
|
||||||
return constants;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "UtilityModule";
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void keepAwakeOn() {
|
|
||||||
final Activity activity = getCurrentActivity();
|
|
||||||
|
|
||||||
if (activity != null) {
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void keepAwakeOff() {
|
|
||||||
final Activity activity = getCurrentActivity();
|
|
||||||
|
|
||||||
if (activity != null) {
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void hideNavigationBar() {
|
|
||||||
final Activity activity = MainActivity.getActivity();
|
|
||||||
if (activity != null) {
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
View decorView = activity.getWindow().getDecorView();
|
|
||||||
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
|
||||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void showNavigationBar() {
|
|
||||||
final Activity activity = MainActivity.getActivity();
|
|
||||||
if (activity != null) {
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
View decorView = activity.getWindow().getDecorView();
|
|
||||||
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
|
||||||
View.SYSTEM_UI_FLAG_VISIBLE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void getDeviceId(boolean requestPermission, final Promise promise) {
|
|
||||||
if (isEmulator()) {
|
|
||||||
promise.reject("Rewards cannot be claimed from an emulator nor virtual device.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
|
||||||
String id = null;
|
|
||||||
try {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
id = telephonyManager.getImei(); // GSM
|
|
||||||
if (id == null) {
|
|
||||||
id = telephonyManager.getMeid(); // CDMA
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
id = telephonyManager.getDeviceId();
|
|
||||||
}
|
|
||||||
} catch (SecurityException ex) {
|
|
||||||
// Maybe the permission was not granted? Try to acquire permission
|
|
||||||
/*if (requestPermission) {
|
|
||||||
requestPhoneStatePermission();
|
|
||||||
}*/
|
|
||||||
} catch (Exception ex) {
|
|
||||||
// id could not be obtained. Display a warning that rewards cannot be claimed.
|
|
||||||
promise.reject(ex.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (id == null || id.trim().length() == 0) {
|
|
||||||
promise.reject("Rewards cannot be claimed because your device could not be identified.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
promise.resolve(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void canReceiveSms(final Promise promise) {
|
|
||||||
promise.resolve(MainActivity.hasPermission(Manifest.permission.RECEIVE_SMS, MainActivity.getActivity()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void requestReceiveSmsPermission() {
|
|
||||||
MainActivity activity = (MainActivity) MainActivity.getActivity();
|
|
||||||
if (activity != null) {
|
|
||||||
// Request for the RECEIVE_SMS permission
|
|
||||||
MainActivity.checkReceiveSmsPermission(activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void canReadWriteStorage(final Promise promise) {
|
|
||||||
promise.resolve(MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, MainActivity.getActivity()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void requestStoragePermission() {
|
|
||||||
MainActivity activity = (MainActivity) MainActivity.getActivity();
|
|
||||||
if (activity != null) {
|
|
||||||
MainActivity.checkStoragePermission(activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void shareLogFile(Callback errorCallback) {
|
|
||||||
String logFileName = "lbrynet.log";
|
|
||||||
File logFile = new File(String.format("%s/%s", Utils.getAppInternalStorageDir(context), "lbrynet"), logFileName);
|
|
||||||
if (!logFile.exists()) {
|
|
||||||
errorCallback.invoke("The lbrynet.log file could not be found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Uri fileUri = FileProvider.getUriForFile(context, FILE_PROVIDER, logFile);
|
|
||||||
if (fileUri != null) {
|
|
||||||
Intent shareIntent = new Intent();
|
|
||||||
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
shareIntent.setAction(Intent.ACTION_SEND);
|
|
||||||
shareIntent.setType("text/plain");
|
|
||||||
shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
|
||||||
|
|
||||||
Intent sendLogIntent = Intent.createChooser(shareIntent, "Send LBRY log");
|
|
||||||
sendLogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
context.startActivity(sendLogIntent);
|
|
||||||
}
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
errorCallback.invoke("The lbrynet.log file cannot be shared due to permission restrictions.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void shareUrl(String url) {
|
|
||||||
Intent shareIntent = new Intent();
|
|
||||||
shareIntent.setAction(Intent.ACTION_SEND);
|
|
||||||
shareIntent.setType("text/plain");
|
|
||||||
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
|
|
||||||
|
|
||||||
Intent shareUrlIntent = Intent.createChooser(shareIntent, "Share LBRY content");
|
|
||||||
shareUrlIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
context.startActivity(shareUrlIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void showNotificationForContent(final String uri, String title, String publisher, final String thumbnail, boolean isPlayable) {
|
|
||||||
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
NotificationChannel channel = new NotificationChannel(
|
|
||||||
NOTIFICATION_CHANNEL_ID, "LBRY Subscriptions", NotificationManager.IMPORTANCE_DEFAULT);
|
|
||||||
channel.setDescription("LBRY subscription notifications");
|
|
||||||
notificationManager.createNotificationChannel(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeNotifications.containsKey(uri)) {
|
|
||||||
// the notification for the specified uri is already present, don't try to create another one
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int id = 0;
|
|
||||||
Random random = new Random();
|
|
||||||
do {
|
|
||||||
id = random.nextInt();
|
|
||||||
} while (id < 100);
|
|
||||||
final int notificationId = id;
|
|
||||||
|
|
||||||
String uriWithParam = String.format("%s?download=true", uri);
|
|
||||||
Intent playIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uriWithParam));
|
|
||||||
playIntent.putExtra(MainActivity.SOURCE_NOTIFICATION_ID_KEY, notificationId);
|
|
||||||
playIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
|
||||||
PendingIntent playPendingIntent = PendingIntent.getActivity(context, 0, playIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
|
||||||
|
|
||||||
boolean hasThumbnail = false;
|
|
||||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
|
||||||
builder.setAutoCancel(true)
|
|
||||||
.setColor(ContextCompat.getColor(context, R.color.lbryGreen))
|
|
||||||
.setContentIntent(DownloadManager.getLaunchPendingIntent(uri, context))
|
|
||||||
.setContentTitle(publisher)
|
|
||||||
.setContentText(title)
|
|
||||||
.setSmallIcon(R.drawable.ic_lbry)
|
|
||||||
.addAction(android.R.drawable.ic_media_play, (isPlayable ? "Play" : "Open"), playPendingIntent);
|
|
||||||
|
|
||||||
activeNotifications.put(uri, notificationId);
|
|
||||||
if (thumbnail != null) {
|
|
||||||
// attempt to load the thumbnail Bitmap before displaying the notification
|
|
||||||
final Uri thumbnailUri = Uri.parse(thumbnail);
|
|
||||||
if (thumbnailUri != null) {
|
|
||||||
hasThumbnail = true;
|
|
||||||
(new AsyncTask<Void, Void, Bitmap>() {
|
|
||||||
protected Bitmap doInBackground(Void... params) {
|
|
||||||
try {
|
|
||||||
return Picasso.get().load(thumbnailUri).get();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onPostExecute(Bitmap result) {
|
|
||||||
if (result != null) {
|
|
||||||
builder.setLargeIcon(result)
|
|
||||||
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(result).bigLargeIcon(null));
|
|
||||||
}
|
|
||||||
notificationManager.notify(notificationId, builder.build());
|
|
||||||
}
|
|
||||||
}).execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasThumbnail) {
|
|
||||||
notificationManager.notify(notificationId, builder.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isEmulator() {
|
|
||||||
String buildModel = Build.MODEL.toLowerCase();
|
|
||||||
return (// Check FINGERPRINT
|
|
||||||
Build.FINGERPRINT.startsWith("generic") ||
|
|
||||||
Build.FINGERPRINT.startsWith("unknown") ||
|
|
||||||
Build.FINGERPRINT.contains("test-keys") ||
|
|
||||||
|
|
||||||
// Check MODEL
|
|
||||||
buildModel.contains("google_sdk") ||
|
|
||||||
buildModel.contains("emulator") ||
|
|
||||||
buildModel.contains("android sdk built for x86") ||
|
|
||||||
|
|
||||||
// Check MANUFACTURER
|
|
||||||
Build.MANUFACTURER.contains("Genymotion") ||
|
|
||||||
"unknown".equals(Build.MANUFACTURER) ||
|
|
||||||
|
|
||||||
// Check HARDWARE
|
|
||||||
Build.HARDWARE.contains("goldfish") ||
|
|
||||||
Build.HARDWARE.contains("vbox86") ||
|
|
||||||
|
|
||||||
// Check PRODUCT
|
|
||||||
"google_sdk".equals(Build.PRODUCT) ||
|
|
||||||
"sdk_google_phone_x86".equals(Build.PRODUCT) ||
|
|
||||||
"sdk".equals(Build.PRODUCT) ||
|
|
||||||
"sdk_x86".equals(Build.PRODUCT) ||
|
|
||||||
"vbox86p".equals(Build.PRODUCT) ||
|
|
||||||
|
|
||||||
// Check BRAND and DEVICE
|
|
||||||
(Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void setSecureValue(String key, String value) {
|
|
||||||
if (keyStore != null) {
|
|
||||||
Utils.setSecureValue(key, value, context, keyStore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void getSecureValue(String key, Promise promise) {
|
|
||||||
if (keyStore == null) {
|
|
||||||
promise.reject("no keyStore found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
promise.resolve(Utils.getSecureValue(key, context, keyStore));
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void checkDownloads() {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setAction(LbrynetService.ACTION_CHECK_DOWNLOADS);
|
|
||||||
if (context != null) {
|
|
||||||
context.sendBroadcast(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void queueDownload(String outpoint) {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setAction(LbrynetService.ACTION_QUEUE_DOWNLOAD);
|
|
||||||
intent.putExtra("outpoint", outpoint);
|
|
||||||
|
|
||||||
if (context != null) {
|
|
||||||
context.sendBroadcast(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void deleteDownload(String uri) {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setAction(LbrynetService.ACTION_DELETE_DOWNLOAD);
|
|
||||||
intent.putExtra("uri", uri);
|
|
||||||
if (context != null) {
|
|
||||||
context.sendBroadcast(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void openDocumentPicker(String type) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
|
||||||
intent.setType(type);
|
|
||||||
Activity activity = MainActivity.getActivity();
|
|
||||||
if (activity != null) {
|
|
||||||
activity.startActivityForResult(
|
|
||||||
Intent.createChooser(intent, "Select a file"), MainActivity.DOCUMENT_PICKER_RESULT_CODE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void setNativeBooleanSetting(String key, final boolean value) {
|
|
||||||
if (context != null) {
|
|
||||||
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
|
||||||
SharedPreferences.Editor editor = sp.edit();
|
|
||||||
editor.putBoolean(key, value);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DHT_ENABLED.equalsIgnoreCase(key)) {
|
|
||||||
(new AsyncTask<Void, Void, Void>() {
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
String fileContent = value ? "on" : "off";
|
|
||||||
String path = String.format("%s/%s", Utils.getAppInternalStorageDir(context), "dht");
|
|
||||||
PrintStream out = null;
|
|
||||||
try {
|
|
||||||
out = new PrintStream(new FileOutputStream(path));
|
|
||||||
out.print(fileContent);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
// pass
|
|
||||||
} finally {
|
|
||||||
if (out != null) {
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void getNativeBooleanSetting(String key, boolean defaultValue, Promise promise) {
|
|
||||||
if (context != null) {
|
|
||||||
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
|
||||||
promise.resolve(sp.getBoolean(key, defaultValue));
|
|
||||||
} else {
|
|
||||||
promise.resolve(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void setNativeStringSetting(String key, String value) {
|
|
||||||
if (context != null) {
|
|
||||||
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
|
||||||
SharedPreferences.Editor editor = sp.edit();
|
|
||||||
editor.putString(key, value);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void getNativeStringSetting(String key, String defaultValue, Promise promise) {
|
|
||||||
if (context != null) {
|
|
||||||
SharedPreferences sp = context.getSharedPreferences(MainActivity.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
|
||||||
promise.resolve(sp.getString(key, defaultValue));
|
|
||||||
} else {
|
|
||||||
promise.resolve(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void getNotificationLaunchTarget(Promise promise) {
|
|
||||||
Activity activity = MainActivity.getActivity();
|
|
||||||
if (activity != null) {
|
|
||||||
Intent intent = activity.getIntent();
|
|
||||||
if (intent != null) {
|
|
||||||
String target = intent.getStringExtra("target");
|
|
||||||
if (target != null && target.trim().length() > 0) {
|
|
||||||
promise.resolve(target);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
promise.resolve(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void getDownloadDirectory(Promise promise) {
|
|
||||||
// This obtains a public default download directory after the storage permission has been granted
|
|
||||||
promise.resolve(Utils.getConfiguredDownloadDirectory(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void getLbrynetDirectory(Promise promise) {
|
|
||||||
String path = String.format("%s/%s", Utils.getAppInternalStorageDir(context), "lbrynet");
|
|
||||||
promise.resolve(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void getPlatform(Promise promise) {
|
|
||||||
String platform = String.format("Android %s (API %s)", Utils.getAndroidRelease(), Utils.getAndroidSdk());
|
|
||||||
promise.resolve(platform);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void checkSdkReady() {
|
|
||||||
// check that the sdk ready when the service is already running so that we can send the ready event
|
|
||||||
ReactContext reactContext = (ReactContext) context;
|
|
||||||
if (MainActivity.lbrySdkReady && reactContext != null) {
|
|
||||||
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("onSdkReady", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void log(String tag, String message) {
|
|
||||||
android.util.Log.d(tag, message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package io.lbry.browser.reactmodules;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
|
|
||||||
import com.facebook.react.bridge.Promise;
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
||||||
import com.facebook.react.bridge.ReactMethod;
|
|
||||||
|
|
||||||
public class VersionInfoModule extends ReactContextBaseJavaModule {
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
public VersionInfoModule(ReactApplicationContext reactContext) {
|
|
||||||
super(reactContext);
|
|
||||||
this.context = reactContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "VersionInfo";
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void getAppVersion(final Promise promise) {
|
|
||||||
PackageManager packageManager = this.context.getPackageManager();
|
|
||||||
String packageName = this.context.getPackageName();
|
|
||||||
try {
|
|
||||||
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
|
|
||||||
promise.resolve(packageInfo.versionName);
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
// normally shouldn't happen
|
|
||||||
promise.resolve("Unknown");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package io.lbry.browser.reactpackages;
|
|
||||||
|
|
||||||
import com.facebook.react.ReactPackage;
|
|
||||||
import com.facebook.react.bridge.NativeModule;
|
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
|
||||||
import com.facebook.react.uimanager.ViewManager;
|
|
||||||
|
|
||||||
import io.lbry.browser.reactmodules.BackgroundMediaModule;
|
|
||||||
import io.lbry.browser.reactmodules.DaemonServiceControlModule;
|
|
||||||
import io.lbry.browser.reactmodules.FirstRunModule;
|
|
||||||
import io.lbry.browser.reactmodules.FirebaseModule;
|
|
||||||
import io.lbry.browser.reactmodules.GalleryModule;
|
|
||||||
import io.lbry.browser.reactmodules.RequestsModule;
|
|
||||||
import io.lbry.browser.reactmodules.ScreenOrientationModule;
|
|
||||||
import io.lbry.browser.reactmodules.StatePersistorModule;
|
|
||||||
import io.lbry.browser.reactmodules.VersionInfoModule;
|
|
||||||
import io.lbry.browser.reactmodules.UtilityModule;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class LbryReactPackage implements ReactPackage {
|
|
||||||
@Override
|
|
||||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
|
||||||
List<NativeModule> modules = new ArrayList<>();
|
|
||||||
|
|
||||||
modules.add(new BackgroundMediaModule(reactContext));
|
|
||||||
modules.add(new DaemonServiceControlModule(reactContext));
|
|
||||||
modules.add(new FirstRunModule(reactContext));
|
|
||||||
modules.add(new FirebaseModule(reactContext));
|
|
||||||
modules.add(new GalleryModule(reactContext));
|
|
||||||
modules.add(new RequestsModule(reactContext));
|
|
||||||
modules.add(new ScreenOrientationModule(reactContext));
|
|
||||||
modules.add(new StatePersistorModule(reactContext));
|
|
||||||
modules.add(new UtilityModule(reactContext));
|
|
||||||
modules.add(new VersionInfoModule(reactContext));
|
|
||||||
|
|
||||||
return modules;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package io.lbry.browser.receivers;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import io.lbry.browser.DownloadManager;
|
|
||||||
|
|
||||||
public class NotificationDeletedReceiver extends BroadcastReceiver {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
int notificationId = intent.getExtras().getInt(DownloadManager.NOTIFICATION_ID_KEY);
|
|
||||||
if (DownloadManager.DOWNLOAD_NOTIFICATION_GROUP_ID == notificationId) {
|
|
||||||
DownloadManager.groupCreated = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package io.lbry.browser.tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteException;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.browser.data.DatabaseHelper;
|
||||||
|
import io.lbry.browser.exceptions.LbryioRequestException;
|
||||||
|
import io.lbry.browser.exceptions.LbryioResponseException;
|
||||||
|
import io.lbry.browser.model.lbryinc.Subscription;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbryio;
|
||||||
|
|
||||||
|
public class ChannelSubscribeTask extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
private Context context;
|
||||||
|
private String channelClaimId;
|
||||||
|
private Subscription subscription;
|
||||||
|
private ChannelSubscribeHandler handler;
|
||||||
|
private Exception error;
|
||||||
|
private boolean isUnsubscribing;
|
||||||
|
|
||||||
|
public ChannelSubscribeTask(Context context, String channelClaimId, Subscription subscription, boolean isUnsubscribing, ChannelSubscribeHandler handler) {
|
||||||
|
this.context = context;
|
||||||
|
this.channelClaimId = channelClaimId;
|
||||||
|
this.subscription = subscription;
|
||||||
|
this.handler = handler;
|
||||||
|
this.isUnsubscribing = isUnsubscribing;
|
||||||
|
}
|
||||||
|
protected Boolean doInBackground(Void... params) {
|
||||||
|
SQLiteDatabase db = null;
|
||||||
|
try {
|
||||||
|
// Save to (or delete from) local store
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
db = ((MainActivity) context).getDbHelper().getWritableDatabase();
|
||||||
|
}
|
||||||
|
if (db != null) {
|
||||||
|
if (!isUnsubscribing) {
|
||||||
|
DatabaseHelper.createOrUpdateSubscription(subscription, db);
|
||||||
|
} else {
|
||||||
|
DatabaseHelper.deleteSubscription(subscription, db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save with Lbryio
|
||||||
|
Map<String, String> options = new HashMap<>();
|
||||||
|
options.put("claim_id", channelClaimId);
|
||||||
|
if (!isUnsubscribing) {
|
||||||
|
options.put("channel_name", subscription.getChannelName());
|
||||||
|
}
|
||||||
|
|
||||||
|
String action = isUnsubscribing ? "delete" : "new";
|
||||||
|
Lbryio.call("subscription", action, options, context);
|
||||||
|
} catch (LbryioRequestException | LbryioResponseException | SQLiteException ex) {
|
||||||
|
error = ex;
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
Helper.closeDatabase(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
protected void onPostExecute(Boolean success) {
|
||||||
|
if (handler != null) {
|
||||||
|
if (success) {
|
||||||
|
handler.onSuccess();
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ChannelSubscribeHandler {
|
||||||
|
void onSuccess();
|
||||||
|
void onError(Exception exception);
|
||||||
|
}
|
||||||
|
}
|
54
app/src/main/java/io/lbry/browser/tasks/ClaimSearchTask.java
Normal file
54
app/src/main/java/io/lbry/browser/tasks/ClaimSearchTask.java
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package io.lbry.browser.tasks;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.ApiCallException;
|
||||||
|
import io.lbry.browser.model.Claim;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
|
||||||
|
public class ClaimSearchTask extends AsyncTask<Void, Void, List<Claim>> {
|
||||||
|
private Map<String, Object> options;
|
||||||
|
private String connectionString;
|
||||||
|
private ClaimSearchResultHandler handler;
|
||||||
|
private View progressView;
|
||||||
|
private ApiCallException error;
|
||||||
|
|
||||||
|
public ClaimSearchTask(Map<String, Object> options, String connectionString, View progressView, ClaimSearchResultHandler handler) {
|
||||||
|
this.options = options;
|
||||||
|
this.connectionString = connectionString;
|
||||||
|
this.progressView = progressView;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressView, View.VISIBLE);
|
||||||
|
}
|
||||||
|
protected List<Claim> doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
return Lbry.claimSearch(options, connectionString);
|
||||||
|
} catch (ApiCallException ex) {
|
||||||
|
error = ex;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void onPostExecute(List<Claim> claims) {
|
||||||
|
Helper.setViewVisibility(progressView, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (claims != null) {
|
||||||
|
handler.onSuccess(claims, claims.size() < Helper.parseInt(options.get("page_size"), 0));
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ClaimSearchResultHandler {
|
||||||
|
void onSuccess(List<Claim> claims, boolean hasReachedEnd);
|
||||||
|
void onError(Exception error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package io.lbry.browser.tasks;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import io.lbry.browser.model.lbryinc.User;
|
||||||
|
import io.lbry.browser.utils.Lbryio;
|
||||||
|
|
||||||
|
public class FetchCurrentUserTask extends AsyncTask<Void, Void, User> {
|
||||||
|
private Exception error;
|
||||||
|
private FetchUserTaskHandler handler;
|
||||||
|
|
||||||
|
public FetchCurrentUserTask(FetchUserTaskHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
protected User doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
return Lbryio.fetchCurrentUser(null);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
error = ex;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(User result) {
|
||||||
|
if (handler != null) {
|
||||||
|
if (result != null) {
|
||||||
|
handler.onSuccess(result);
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface FetchUserTaskHandler {
|
||||||
|
void onSuccess(User user);
|
||||||
|
void onError(Exception error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package io.lbry.browser.tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.browser.data.DatabaseHelper;
|
||||||
|
import io.lbry.browser.exceptions.LbryioRequestException;
|
||||||
|
import io.lbry.browser.exceptions.LbryioResponseException;
|
||||||
|
import io.lbry.browser.model.lbryinc.Subscription;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.LbryUri;
|
||||||
|
import io.lbry.browser.utils.Lbryio;
|
||||||
|
|
||||||
|
public class FetchSubscriptionsTask extends AsyncTask<Void, Void, List<Subscription>> {
|
||||||
|
private Context context;
|
||||||
|
private FetchSubscriptionsHandler handler;
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
private Exception error;
|
||||||
|
|
||||||
|
public FetchSubscriptionsTask(Context context, ProgressBar progressBar, FetchSubscriptionsHandler handler) {
|
||||||
|
this.context = context;
|
||||||
|
this.progressBar = progressBar;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressBar, View.VISIBLE);
|
||||||
|
}
|
||||||
|
protected List<Subscription> doInBackground(Void... params) {
|
||||||
|
List<Subscription> subscriptions = new ArrayList<>();
|
||||||
|
SQLiteDatabase db = null;
|
||||||
|
try {
|
||||||
|
JSONArray array = (JSONArray) Lbryio.parseResponse(Lbryio.call("subscription", "list", context));
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
db = ((MainActivity) context).getDbHelper().getWritableDatabase();
|
||||||
|
}
|
||||||
|
if (array != null) {
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
JSONObject item = array.getJSONObject(i);
|
||||||
|
String claimId = item.getString("claim_id");
|
||||||
|
String channelName = item.getString("channel_name");
|
||||||
|
|
||||||
|
LbryUri url = new LbryUri();
|
||||||
|
url.setChannelName(channelName);
|
||||||
|
url.setClaimId(claimId);
|
||||||
|
Subscription subscription = new Subscription(channelName, url.toString());
|
||||||
|
subscriptions.add(subscription);
|
||||||
|
// Persist the subscription locally if it doesn't exist
|
||||||
|
if (db != null) {
|
||||||
|
DatabaseHelper.createOrUpdateSubscription(subscription, db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (LbryioRequestException | LbryioResponseException | JSONException | ClassCastException ex) {
|
||||||
|
error = ex;
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
Helper.closeDatabase(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
return subscriptions;
|
||||||
|
}
|
||||||
|
protected void onPostExecute(List<Subscription> subscriptions) {
|
||||||
|
Helper.setViewVisibility(progressBar, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (subscriptions != null) {
|
||||||
|
handler.onSuccess(subscriptions);
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface FetchSubscriptionsHandler {
|
||||||
|
void onSuccess(List<Subscription> subscriptions);
|
||||||
|
void onError(Exception exception);
|
||||||
|
}
|
||||||
|
}
|
54
app/src/main/java/io/lbry/browser/tasks/FileListTask.java
Normal file
54
app/src/main/java/io/lbry/browser/tasks/FileListTask.java
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package io.lbry.browser.tasks;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.ApiCallException;
|
||||||
|
import io.lbry.browser.model.File;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
|
||||||
|
public class FileListTask extends AsyncTask<Void, Void, List<File>> {
|
||||||
|
private String claimId;
|
||||||
|
private FileListResultHandler handler;
|
||||||
|
private View progressView;
|
||||||
|
private ApiCallException error;
|
||||||
|
|
||||||
|
public FileListTask(View progressView, FileListResultHandler handler) {
|
||||||
|
this(null, progressView, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileListTask(String claimId, View progressView, FileListResultHandler handler) {
|
||||||
|
this.claimId = claimId;
|
||||||
|
this.progressView = progressView;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressView, View.VISIBLE);
|
||||||
|
}
|
||||||
|
protected List<File> doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
return Lbry.fileList(claimId);
|
||||||
|
} catch (ApiCallException ex) {
|
||||||
|
error = ex;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void onPostExecute(List<File> files) {
|
||||||
|
Helper.setViewVisibility(progressView, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (files != null) {
|
||||||
|
handler.onSuccess(files);
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface FileListResultHandler {
|
||||||
|
void onSuccess(List<File> files);
|
||||||
|
void onError(Exception error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package io.lbry.browser.tasks;
|
||||||
|
|
||||||
|
public interface GenericTaskHandler {
|
||||||
|
void beforeStart();
|
||||||
|
void onSuccess();
|
||||||
|
void onError(Exception error);
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package io.lbry.browser.tasks;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.LbryRequestException;
|
||||||
|
import io.lbry.browser.exceptions.LbryResponseException;
|
||||||
|
import io.lbry.browser.model.UrlSuggestion;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lighthouse;
|
||||||
|
|
||||||
|
public class LighthouseAutoCompleteTask extends AsyncTask<Void, Void, List<UrlSuggestion>> {
|
||||||
|
private String text;
|
||||||
|
private AutoCompleteResultHandler handler;
|
||||||
|
private View progressView;
|
||||||
|
private Exception error;
|
||||||
|
|
||||||
|
public LighthouseAutoCompleteTask(String text, View progressView, AutoCompleteResultHandler handler) {
|
||||||
|
this.text = text;
|
||||||
|
this.progressView = progressView;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressView, View.VISIBLE);
|
||||||
|
}
|
||||||
|
protected List<UrlSuggestion> doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
return Lighthouse.autocomplete(text);
|
||||||
|
} catch (LbryRequestException | LbryResponseException ex) {
|
||||||
|
error = ex;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void onPostExecute(List<UrlSuggestion> suggestions) {
|
||||||
|
Helper.setViewVisibility(progressView, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (suggestions != null) {
|
||||||
|
handler.onSuccess(suggestions);
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface AutoCompleteResultHandler {
|
||||||
|
void onSuccess(List<UrlSuggestion> suggestions);
|
||||||
|
void onError(Exception error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package io.lbry.browser.tasks;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.LbryRequestException;
|
||||||
|
import io.lbry.browser.exceptions.LbryResponseException;
|
||||||
|
import io.lbry.browser.model.Claim;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lighthouse;
|
||||||
|
|
||||||
|
public class LighthouseSearchTask extends AsyncTask<Void, Void, List<Claim>> {
|
||||||
|
private String rawQuery;
|
||||||
|
private int size;
|
||||||
|
private int from;
|
||||||
|
private boolean nsfw;
|
||||||
|
private String relatedTo;
|
||||||
|
private ClaimSearchTask.ClaimSearchResultHandler handler;
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
private Exception error;
|
||||||
|
|
||||||
|
public LighthouseSearchTask(String rawQuery, int size, int from, boolean nsfw, String relatedTo, ProgressBar progressBar, ClaimSearchTask.ClaimSearchResultHandler handler) {
|
||||||
|
this.rawQuery = rawQuery;
|
||||||
|
this.size = size;
|
||||||
|
this.from = from;
|
||||||
|
this.nsfw = nsfw;
|
||||||
|
this.relatedTo = relatedTo;
|
||||||
|
this.progressBar = progressBar;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressBar, View.VISIBLE);
|
||||||
|
}
|
||||||
|
protected List<Claim> doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
return Lighthouse.search(rawQuery, size, from, nsfw, relatedTo);
|
||||||
|
} catch (LbryRequestException | LbryResponseException ex) {
|
||||||
|
error = ex;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void onPostExecute(List<Claim> claims) {
|
||||||
|
Helper.setViewVisibility(progressBar, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (claims != null) {
|
||||||
|
handler.onSuccess(claims, claims.size() < size);
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
app/src/main/java/io/lbry/browser/tasks/ResolveTask.java
Normal file
57
app/src/main/java/io/lbry/browser/tasks/ResolveTask.java
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package io.lbry.browser.tasks;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.ApiCallException;
|
||||||
|
import io.lbry.browser.model.Claim;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
|
||||||
|
public class ResolveTask extends AsyncTask<Void, Void, List<Claim>> {
|
||||||
|
private List<String> urls;
|
||||||
|
private String connectionString;
|
||||||
|
private ResolveResultHandler handler;
|
||||||
|
private View progressView;
|
||||||
|
private ApiCallException error;
|
||||||
|
|
||||||
|
public ResolveTask(String url, String connectionString, View progressView, ResolveResultHandler handler) {
|
||||||
|
this(Arrays.asList(url), connectionString, progressView, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResolveTask(List<String> urls, String connectionString, View progressView, ResolveResultHandler handler) {
|
||||||
|
this.urls = urls;
|
||||||
|
this.connectionString = connectionString;
|
||||||
|
this.progressView = progressView;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressView, View.VISIBLE);
|
||||||
|
}
|
||||||
|
protected List<Claim> doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
return Lbry.resolve(urls, connectionString);
|
||||||
|
} catch (ApiCallException ex) {
|
||||||
|
error = ex;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void onPostExecute(List<Claim> claims) {
|
||||||
|
Helper.setViewVisibility(progressView, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (claims != null) {
|
||||||
|
handler.onSuccess(claims);
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ResolveResultHandler {
|
||||||
|
void onSuccess(List<Claim> claims);
|
||||||
|
void onError(Exception error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package io.lbry.browser.tasks.verification;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import io.lbry.browser.model.lbryinc.User;
|
||||||
|
import io.lbry.browser.utils.Lbryio;
|
||||||
|
|
||||||
|
public class CheckUserEmailVerifiedTask extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
private CheckUserEmailVerifiedHandler handler;
|
||||||
|
|
||||||
|
public CheckUserEmailVerifiedTask(CheckUserEmailVerifiedHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Boolean doInBackground(Void... params) {
|
||||||
|
User user = Lbryio.fetchCurrentUser(null);
|
||||||
|
return user != null && user.isHasVerifiedEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(Boolean result) {
|
||||||
|
if (handler != null && result) {
|
||||||
|
// we only care if the user has actually verified their email
|
||||||
|
handler.onUserEmailVerified();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface CheckUserEmailVerifiedHandler {
|
||||||
|
void onUserEmailVerified();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package io.lbry.browser.tasks.verification;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.LbryioRequestException;
|
||||||
|
import io.lbry.browser.exceptions.LbryioResponseException;
|
||||||
|
import io.lbry.browser.tasks.GenericTaskHandler;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbryio;
|
||||||
|
|
||||||
|
public class EmailNewTask extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
private String email;
|
||||||
|
private View progressView;
|
||||||
|
private EmailNewHandler handler;
|
||||||
|
private Exception error;
|
||||||
|
|
||||||
|
public EmailNewTask(String email, View progressView, EmailNewHandler handler) {
|
||||||
|
this.email = email;
|
||||||
|
this.progressView = progressView;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressView, View.VISIBLE);
|
||||||
|
if (handler != null) {
|
||||||
|
handler.beforeStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected Boolean doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
Map<String, String> options = new HashMap<>();
|
||||||
|
options.put("email", email);
|
||||||
|
options.put("send_verification_email", "true");
|
||||||
|
Lbryio.parseResponse(Lbryio.call("user_email", "new", options, Helper.METHOD_POST, null));
|
||||||
|
} catch (LbryioResponseException ex) {
|
||||||
|
if (ex.getStatusCode() == 409) {
|
||||||
|
if (handler != null) {
|
||||||
|
handler.onEmailExists();
|
||||||
|
}
|
||||||
|
|
||||||
|
// email already exists
|
||||||
|
Map<String, String> options = new HashMap<>();
|
||||||
|
options.put("email", email);
|
||||||
|
options.put("only_if_expired", "true");
|
||||||
|
try {
|
||||||
|
Lbryio.parseResponse(Lbryio.call("user_email", "resend_token", options, Helper.METHOD_POST, null));
|
||||||
|
} catch (LbryioRequestException | LbryioResponseException e) {
|
||||||
|
error = e;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error = ex;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (LbryioRequestException ex) {
|
||||||
|
error = ex;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
protected void onPostExecute(Boolean result) {
|
||||||
|
Helper.setViewVisibility(progressView, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (result) {
|
||||||
|
handler.onSuccess();
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface EmailNewHandler extends GenericTaskHandler {
|
||||||
|
void onEmailExists();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package io.lbry.browser.tasks.verification;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.LbryioRequestException;
|
||||||
|
import io.lbry.browser.exceptions.LbryioResponseException;
|
||||||
|
import io.lbry.browser.tasks.GenericTaskHandler;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbryio;
|
||||||
|
|
||||||
|
public class EmailResendTask extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
private String email;
|
||||||
|
private View progressView;
|
||||||
|
private GenericTaskHandler handler;
|
||||||
|
private Exception error;
|
||||||
|
|
||||||
|
public EmailResendTask(String email, View progressView, GenericTaskHandler handler) {
|
||||||
|
this.email = email;
|
||||||
|
this.progressView = progressView;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressView, View.VISIBLE);
|
||||||
|
if (handler != null) {
|
||||||
|
handler.beforeStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected Boolean doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
Map<String, String> options = new HashMap<>();
|
||||||
|
options.put("email", email);
|
||||||
|
Lbryio.parseResponse(Lbryio.call("user_email", "resend_token", options, Helper.METHOD_POST, null));
|
||||||
|
} catch (LbryioRequestException | LbryioResponseException ex) {
|
||||||
|
error = ex;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
protected void onPostExecute(Boolean result) {
|
||||||
|
Helper.setViewVisibility(progressView, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (result) {
|
||||||
|
handler.onSuccess();
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package io.lbry.browser.tasks.wallet;
|
||||||
|
|
||||||
|
import io.lbry.browser.model.WalletSync;
|
||||||
|
|
||||||
|
public abstract class DefaultSyncTaskHandler implements SyncTaskHandler {
|
||||||
|
public void onSyncGetSuccess(WalletSync walletSync) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
public void onSyncGetWalletNotFound() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
public void onSyncGetError(Exception error) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
public void onSyncSetSuccess(String hash) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
public void onSyncSetError(Exception error) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
public void onSyncApplySuccess(String hash, String data) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
public void onSyncApplyError(Exception error) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package io.lbry.browser.tasks.wallet;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.ApiCallException;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
|
||||||
|
public class SyncApplyTask extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
// flag to indicate if this sync_apply is to fetch wallet data or apply data
|
||||||
|
private boolean fetch;
|
||||||
|
private Exception error;
|
||||||
|
private String password;
|
||||||
|
private String data;
|
||||||
|
private View progressView;
|
||||||
|
private SyncTaskHandler handler;
|
||||||
|
|
||||||
|
private String syncHash;
|
||||||
|
private String syncData;
|
||||||
|
|
||||||
|
public SyncApplyTask(boolean fetch, SyncTaskHandler handler) {
|
||||||
|
this.fetch = fetch;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyncApplyTask(String password, String data, View progressView, SyncTaskHandler handler) {
|
||||||
|
this.password = password;
|
||||||
|
this.data = data;
|
||||||
|
this.progressView = progressView;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressView, View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean doInBackground(Void... params) {
|
||||||
|
Map<String, Object> options = new HashMap<>();
|
||||||
|
options.put("password", Helper.isNullOrEmpty(password) ? "" : password);
|
||||||
|
if (!fetch) {
|
||||||
|
options.put("data", data);
|
||||||
|
options.put("blocking", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject response = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_SYNC_APPLY, options);
|
||||||
|
syncHash = Helper.getJSONString("hash", null, response);
|
||||||
|
syncData = Helper.getJSONString("data", null, response);
|
||||||
|
} catch (ApiCallException ex) {
|
||||||
|
error = ex;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(Boolean result) {
|
||||||
|
Helper.setViewVisibility(progressView, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (result) {
|
||||||
|
handler.onSyncApplySuccess(syncHash, syncData);
|
||||||
|
} else {
|
||||||
|
handler.onSyncApplyError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
116
app/src/main/java/io/lbry/browser/tasks/wallet/SyncGetTask.java
Normal file
116
app/src/main/java/io/lbry/browser/tasks/wallet/SyncGetTask.java
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package io.lbry.browser.tasks.wallet;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.ApiCallException;
|
||||||
|
import io.lbry.browser.exceptions.LbryioRequestException;
|
||||||
|
import io.lbry.browser.exceptions.LbryioResponseException;
|
||||||
|
import io.lbry.browser.exceptions.WalletException;
|
||||||
|
import io.lbry.browser.model.WalletSync;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
import io.lbry.browser.utils.Lbryio;
|
||||||
|
|
||||||
|
public class SyncGetTask extends AsyncTask<Void, Void, WalletSync> {
|
||||||
|
|
||||||
|
private boolean applySyncChanges;
|
||||||
|
private boolean applySyncSuccessful;
|
||||||
|
private Exception error;
|
||||||
|
private Exception syncApplyError;
|
||||||
|
private String password;
|
||||||
|
private SyncTaskHandler handler;
|
||||||
|
private View progressView;
|
||||||
|
|
||||||
|
private String syncHash;
|
||||||
|
private String syncData;
|
||||||
|
|
||||||
|
public SyncGetTask(String password, boolean applySyncChanges, View progressView, SyncTaskHandler handler) {
|
||||||
|
this.password = password;
|
||||||
|
this.progressView = progressView;
|
||||||
|
this.applySyncChanges = applySyncChanges;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressView, View.VISIBLE);
|
||||||
|
}
|
||||||
|
protected WalletSync doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
password = Helper.isNullOrEmpty(password) ? "" : password;
|
||||||
|
JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_WALLET_STATUS);
|
||||||
|
boolean isLocked = Helper.getJSONBoolean("is_locked", false, result);
|
||||||
|
boolean unlockSuccessful =
|
||||||
|
!isLocked || (boolean) Lbry.genericApiCall(Lbry.METHOD_WALLET_UNLOCK, Lbry.buildSingleParam("password", password));
|
||||||
|
if (!unlockSuccessful) {
|
||||||
|
throw new WalletException("The wallet could be unlocked with the provided password.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String hash = (String) Lbry.genericApiCall(Lbry.METHOD_SYNC_HASH);
|
||||||
|
try {
|
||||||
|
JSONObject response = (JSONObject) Lbryio.parseResponse(
|
||||||
|
Lbryio.call("sync", "get", Lbryio.buildSingleParam("hash", hash), Helper.METHOD_POST, null));
|
||||||
|
WalletSync walletSync = new WalletSync(
|
||||||
|
Helper.getJSONString("hash", null, response),
|
||||||
|
Helper.getJSONString("data", null, response),
|
||||||
|
Helper.getJSONBoolean("changed", false, response)
|
||||||
|
);
|
||||||
|
if (applySyncChanges && (!hash.equalsIgnoreCase(walletSync.getHash()) || walletSync.isChanged())) {
|
||||||
|
//Lbry.sync_apply({ password, data: response.data, blocking: true });
|
||||||
|
try {
|
||||||
|
Map<String, Object> options = new HashMap<>();
|
||||||
|
options.put("hash", walletSync.getHash());
|
||||||
|
options.put("data", walletSync.getData());
|
||||||
|
options.put("blocking", true);
|
||||||
|
|
||||||
|
JSONObject syncApplyResponse = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_SYNC_APPLY, options);
|
||||||
|
syncHash = Helper.getJSONString("hash", null, syncApplyResponse);
|
||||||
|
syncData = Helper.getJSONString("data", null, syncApplyResponse);
|
||||||
|
applySyncSuccessful = true;
|
||||||
|
} catch (ApiCallException | ClassCastException ex) {
|
||||||
|
// sync_apply failed
|
||||||
|
syncApplyError = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Lbryio.isSignedIn() && !Lbryio.userHasSyncedWallet) {
|
||||||
|
// indicate that the user owns a synced wallet (only if the user is signed in)
|
||||||
|
Lbryio.userHasSyncedWallet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return walletSync;
|
||||||
|
} catch (LbryioResponseException ex) {
|
||||||
|
// wallet sync data doesn't exist
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (ApiCallException | WalletException | ClassCastException | LbryioRequestException ex) {
|
||||||
|
error = ex;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void onPostExecute(WalletSync result) {
|
||||||
|
Helper.setViewVisibility(progressView, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (result != null) {
|
||||||
|
handler.onSyncGetSuccess(result);
|
||||||
|
} else if (error != null) {
|
||||||
|
handler.onSyncGetError(error);
|
||||||
|
} else {
|
||||||
|
handler.onSyncGetWalletNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applySyncChanges) {
|
||||||
|
if (applySyncSuccessful) {
|
||||||
|
handler.onSyncApplySuccess(syncHash, syncData);
|
||||||
|
} else {
|
||||||
|
handler.onSyncApplyError(syncApplyError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package io.lbry.browser.tasks.wallet;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.LbryioRequestException;
|
||||||
|
import io.lbry.browser.exceptions.LbryioResponseException;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbryio;
|
||||||
|
|
||||||
|
public class SyncSetTask extends AsyncTask<Void, Void, String> {
|
||||||
|
private Exception error;
|
||||||
|
private String oldHash;
|
||||||
|
private String newHash;
|
||||||
|
private String data;
|
||||||
|
private SyncTaskHandler handler;
|
||||||
|
|
||||||
|
public SyncSetTask(String oldHash, String newHash, String data, SyncTaskHandler handler) {
|
||||||
|
this.oldHash = oldHash;
|
||||||
|
this.newHash = newHash;
|
||||||
|
this.data = data;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
Map<String, String> options = new HashMap<>();
|
||||||
|
options.put("old_hash", oldHash);
|
||||||
|
options.put("new_hash", newHash);
|
||||||
|
options.put("data", data);
|
||||||
|
JSONObject response = (JSONObject) Lbryio.parseResponse(
|
||||||
|
Lbryio.call("sync", "set", options, Helper.METHOD_POST, null));
|
||||||
|
String hash = Helper.getJSONString("hash", null, response);
|
||||||
|
return hash;
|
||||||
|
} catch (LbryioRequestException | LbryioResponseException | ClassCastException ex) {
|
||||||
|
error = ex;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected void onPostExecute(String hash) {
|
||||||
|
if (handler != null) {
|
||||||
|
if (!Helper.isNullOrEmpty(hash)) {
|
||||||
|
handler.onSyncSetSuccess(hash);
|
||||||
|
} else if (error != null) {
|
||||||
|
handler.onSyncSetError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package io.lbry.browser.tasks.wallet;
|
||||||
|
|
||||||
|
import io.lbry.browser.model.WalletSync;
|
||||||
|
|
||||||
|
public interface SyncTaskHandler {
|
||||||
|
void onSyncGetSuccess(WalletSync walletSync);
|
||||||
|
void onSyncGetWalletNotFound();
|
||||||
|
void onSyncGetError(Exception error);
|
||||||
|
void onSyncSetSuccess(String hash);
|
||||||
|
void onSyncSetError(Exception error);
|
||||||
|
void onSyncApplySuccess(String hash, String data);
|
||||||
|
void onSyncApplyError(Exception error);
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package io.lbry.browser.tasks.wallet;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.ApiCallException;
|
||||||
|
import io.lbry.browser.model.Transaction;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
|
||||||
|
public class TransactionListTask extends AsyncTask<Void, Void, List<Transaction>> {
|
||||||
|
private int page;
|
||||||
|
private int pageSize;
|
||||||
|
private View progressView;
|
||||||
|
private TransactionListHandler handler;
|
||||||
|
private Exception error;
|
||||||
|
|
||||||
|
public TransactionListTask(int page, int pageSize, View progressView, TransactionListHandler handler) {
|
||||||
|
this.page = page;
|
||||||
|
this.pageSize = pageSize;
|
||||||
|
this.progressView = progressView;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressView, View.VISIBLE);
|
||||||
|
}
|
||||||
|
protected List<Transaction> doInBackground(Void... params) {
|
||||||
|
List<Transaction> transactions = null;
|
||||||
|
try {
|
||||||
|
transactions = Lbry.transactionList(page, pageSize);
|
||||||
|
} catch (ApiCallException ex) {
|
||||||
|
error = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(List<Transaction> transactions) {
|
||||||
|
Helper.setViewVisibility(progressView, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (transactions != null) {
|
||||||
|
handler.onSuccess(transactions, transactions.size() < pageSize);
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface TransactionListHandler {
|
||||||
|
void onSuccess(List<Transaction> transactions, boolean hasReachedEnd);
|
||||||
|
void onError(Exception error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package io.lbry.browser.tasks.wallet;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.ApiCallException;
|
||||||
|
import io.lbry.browser.model.WalletBalance;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
|
||||||
|
public class WalletAddressUnusedTask extends AsyncTask<Void, Void, String> {
|
||||||
|
private WalletAddressUnusedHandler handler;
|
||||||
|
private Exception error;
|
||||||
|
|
||||||
|
public WalletAddressUnusedTask(WalletAddressUnusedHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPreExecute() {
|
||||||
|
if (handler != null) {
|
||||||
|
handler.beforeStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String doInBackground(Void... params) {
|
||||||
|
String address = null;
|
||||||
|
try {
|
||||||
|
address = (String) Lbry.genericApiCall(Lbry.METHOD_ADDRESS_UNUSED);
|
||||||
|
} catch (ApiCallException | ClassCastException ex) {
|
||||||
|
error = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(String unusedAddress) {
|
||||||
|
if (handler != null) {
|
||||||
|
if (!Helper.isNullOrEmpty(unusedAddress)) {
|
||||||
|
handler.onSuccess(unusedAddress);
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface WalletAddressUnusedHandler {
|
||||||
|
void beforeStart();
|
||||||
|
void onSuccess(String newAddress);
|
||||||
|
void onError(Exception error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package io.lbry.browser.tasks.wallet;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.ApiCallException;
|
||||||
|
import io.lbry.browser.model.WalletBalance;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
|
||||||
|
public class WalletBalanceTask extends AsyncTask<Void, Void, WalletBalance> {
|
||||||
|
private WalletBalanceHandler handler;
|
||||||
|
private Exception error;
|
||||||
|
|
||||||
|
public WalletBalanceTask(WalletBalanceHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WalletBalance doInBackground(Void... params) {
|
||||||
|
WalletBalance balance = new WalletBalance();
|
||||||
|
try {
|
||||||
|
JSONObject json = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_WALLET_BALANCE);
|
||||||
|
JSONObject reservedSubtotals = Helper.getJSONObject("reserved_subtotals", json);
|
||||||
|
|
||||||
|
balance.setAvailable(new BigDecimal(Helper.getJSONString("available", "0", json)));
|
||||||
|
balance.setReserved(new BigDecimal(Helper.getJSONString("reserved", "0", json)));
|
||||||
|
balance.setTotal(new BigDecimal(Helper.getJSONString("total", "0", json)));
|
||||||
|
if (reservedSubtotals != null) {
|
||||||
|
balance.setClaims(new BigDecimal(Helper.getJSONString("claims", "0", reservedSubtotals)));
|
||||||
|
balance.setSupports(new BigDecimal(Helper.getJSONString("supports", "0", reservedSubtotals)));
|
||||||
|
balance.setTips(new BigDecimal(Helper.getJSONString("tips", "0", reservedSubtotals)));
|
||||||
|
}
|
||||||
|
} catch (ApiCallException | ClassCastException ex) {
|
||||||
|
error = ex;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(WalletBalance walletBalance) {
|
||||||
|
if (handler != null) {
|
||||||
|
if (walletBalance != null) {
|
||||||
|
handler.onSuccess(walletBalance);
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface WalletBalanceHandler {
|
||||||
|
void onSuccess(WalletBalance walletBalance);
|
||||||
|
void onError(Exception error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package io.lbry.browser.tasks.wallet;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.exceptions.ApiCallException;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
|
||||||
|
public class WalletSendTask extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
private String recipientAddress;
|
||||||
|
private String amount;
|
||||||
|
private View progressView;
|
||||||
|
private WalletSendHandler handler;
|
||||||
|
private Exception error;
|
||||||
|
|
||||||
|
public WalletSendTask(String recipientAddress, String amount, View progressView, WalletSendHandler handler) {
|
||||||
|
this.recipientAddress = recipientAddress;
|
||||||
|
this.amount = amount;
|
||||||
|
this.progressView = progressView;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPreExecute() {
|
||||||
|
Helper.setViewVisibility(progressView, View.VISIBLE);
|
||||||
|
}
|
||||||
|
protected Boolean doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
Map<String, Object> options = new HashMap<>();
|
||||||
|
options.put("addresses", Arrays.asList(recipientAddress));
|
||||||
|
options.put("amount", amount);
|
||||||
|
Lbry.genericApiCall(Lbry.METHOD_WALLET_SEND, options);
|
||||||
|
} catch (ApiCallException ex) {
|
||||||
|
error = ex;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(Boolean result) {
|
||||||
|
Helper.setViewVisibility(progressView, View.GONE);
|
||||||
|
if (handler != null) {
|
||||||
|
if (result) {
|
||||||
|
handler.onSuccess();
|
||||||
|
} else {
|
||||||
|
handler.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface WalletSendHandler {
|
||||||
|
void onSuccess();
|
||||||
|
void onError(Exception error);
|
||||||
|
}
|
||||||
|
}
|
25
app/src/main/java/io/lbry/browser/ui/BaseFragment.java
Normal file
25
app/src/main/java/io/lbry/browser/ui/BaseFragment.java
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package io.lbry.browser.ui;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
public class BaseFragment extends Fragment {
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private Map<String, Object> params;
|
||||||
|
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
Context context = getContext();
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
((MainActivity) context).setSelectedMenuItemForFragment(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,315 @@
|
||||||
|
package io.lbry.browser.ui.allcontent;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.FileViewActivity;
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.browser.R;
|
||||||
|
import io.lbry.browser.adapter.ClaimListAdapter;
|
||||||
|
import io.lbry.browser.dialog.ContentFromDialogFragment;
|
||||||
|
import io.lbry.browser.dialog.ContentSortDialogFragment;
|
||||||
|
import io.lbry.browser.model.Claim;
|
||||||
|
import io.lbry.browser.tasks.ClaimSearchTask;
|
||||||
|
import io.lbry.browser.ui.BaseFragment;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
|
||||||
|
// TODO: Similar code to FollowingFragment and Channel page fragment. Probably make common operations (sorting/filtering) into a control
|
||||||
|
public class AllContentFragment extends BaseFragment {
|
||||||
|
|
||||||
|
private boolean singleTagView;
|
||||||
|
private List<String> tags;
|
||||||
|
private View layoutFilterContainer;
|
||||||
|
private View sortLink;
|
||||||
|
private View contentFromLink;
|
||||||
|
private View scopeLink;
|
||||||
|
private TextView titleView;
|
||||||
|
private TextView sortLinkText;
|
||||||
|
private TextView contentFromLinkText;
|
||||||
|
private TextView scopeLinkText;
|
||||||
|
private RecyclerView contentList;
|
||||||
|
private int currentSortBy;
|
||||||
|
private int currentContentFrom;
|
||||||
|
private int currentScope;
|
||||||
|
private String contentReleaseTime;
|
||||||
|
private List<String> contentSortOrder;
|
||||||
|
private View fromPrefix;
|
||||||
|
private View forPrefix;
|
||||||
|
private View contentLoading;
|
||||||
|
private View bigContentLoading;
|
||||||
|
private ClaimListAdapter contentListAdapter;
|
||||||
|
private boolean contentClaimSearchLoading;
|
||||||
|
private boolean contentHasReachedEnd;
|
||||||
|
private int currentClaimSearchPage;
|
||||||
|
private ClaimSearchTask contentClaimSearchTask;
|
||||||
|
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View root = inflater.inflate(R.layout.fragment_all_content, container, false);
|
||||||
|
|
||||||
|
// All content page is sorted by trending by default, past week if sort is top
|
||||||
|
currentSortBy = ContentSortDialogFragment.ITEM_SORT_BY_TRENDING;
|
||||||
|
currentContentFrom = ContentFromDialogFragment.ITEM_FROM_PAST_WEEK;
|
||||||
|
|
||||||
|
layoutFilterContainer = root.findViewById(R.id.all_content_filter_container);
|
||||||
|
titleView = root.findViewById(R.id.all_content_page_title);
|
||||||
|
sortLink = root.findViewById(R.id.all_content_sort_link);
|
||||||
|
contentFromLink = root.findViewById(R.id.all_content_time_link);
|
||||||
|
scopeLink = root.findViewById(R.id.all_content_scope_link);
|
||||||
|
fromPrefix = root.findViewById(R.id.all_content_from_prefix);
|
||||||
|
forPrefix = root.findViewById(R.id.all_content_for_prefix);
|
||||||
|
|
||||||
|
sortLinkText = root.findViewById(R.id.all_content_sort_link_text);
|
||||||
|
contentFromLinkText = root.findViewById(R.id.all_content_time_link_text);
|
||||||
|
scopeLinkText = root.findViewById(R.id.all_content_scope_link_text);
|
||||||
|
|
||||||
|
bigContentLoading = root.findViewById(R.id.all_content_main_progress);
|
||||||
|
contentLoading = root.findViewById(R.id.all_content_load_progress);
|
||||||
|
|
||||||
|
contentList = root.findViewById(R.id.all_content_list);
|
||||||
|
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||||
|
contentList.setLayoutManager(llm);
|
||||||
|
contentList.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (contentClaimSearchLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
if (lm != null) {
|
||||||
|
int visibleItemCount = lm.getChildCount();
|
||||||
|
int totalItemCount = lm.getItemCount();
|
||||||
|
int pastVisibleItems = lm.findFirstVisibleItemPosition();
|
||||||
|
if (pastVisibleItems + visibleItemCount >= totalItemCount) {
|
||||||
|
if (!contentHasReachedEnd) {
|
||||||
|
// load more
|
||||||
|
currentClaimSearchPage++;
|
||||||
|
fetchClaimSearchContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sortLink.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
ContentSortDialogFragment dialog = ContentSortDialogFragment.newInstance();
|
||||||
|
dialog.setCurrentSortByItem(currentSortBy);
|
||||||
|
dialog.setSortByListener(new ContentSortDialogFragment.SortByListener() {
|
||||||
|
@Override
|
||||||
|
public void onSortByItemSelected(int sortBy) {
|
||||||
|
onSortByChanged(sortBy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Context context = getContext();
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
MainActivity activity = (MainActivity) context;
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), ContentSortDialogFragment.TAG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
contentFromLink.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
ContentFromDialogFragment dialog = ContentFromDialogFragment.newInstance();
|
||||||
|
dialog.setCurrentFromItem(currentContentFrom);
|
||||||
|
dialog.setContentFromListener(new ContentFromDialogFragment.ContentFromListener() {
|
||||||
|
@Override
|
||||||
|
public void onContentFromItemSelected(int contentFromItem) {
|
||||||
|
onContentFromChanged(contentFromItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Context context = getContext();
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
MainActivity activity = (MainActivity) context;
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), ContentFromDialogFragment.TAG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
checkParams(false);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParams(Map<String, Object> params) {
|
||||||
|
super.setParams(params);
|
||||||
|
if (getView() != null) {
|
||||||
|
checkParams(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkParams(boolean reload) {
|
||||||
|
Map<String, Object> params = getParams();
|
||||||
|
if (params != null && params.containsKey("singleTag")) {
|
||||||
|
String tagName = params.get("singleTag").toString();
|
||||||
|
singleTagView = true;
|
||||||
|
tags = Arrays.asList(tagName);
|
||||||
|
titleView.setText(Helper.capitalize(tagName));
|
||||||
|
} else {
|
||||||
|
singleTagView = false;
|
||||||
|
tags = null;
|
||||||
|
titleView.setText(getString(R.string.all_content));
|
||||||
|
}
|
||||||
|
|
||||||
|
forPrefix.setVisibility(singleTagView ? View.GONE : View.VISIBLE);
|
||||||
|
scopeLink.setVisibility(singleTagView ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
|
if (reload) {
|
||||||
|
fetchClaimSearchContent(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onContentFromChanged(int contentFrom) {
|
||||||
|
currentContentFrom = contentFrom;
|
||||||
|
|
||||||
|
// rebuild options and search
|
||||||
|
updateContentFromLinkText();
|
||||||
|
contentReleaseTime = Helper.buildReleaseTime(currentContentFrom);
|
||||||
|
fetchClaimSearchContent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSortByChanged(int sortBy) {
|
||||||
|
currentSortBy = sortBy;
|
||||||
|
|
||||||
|
// rebuild options and search
|
||||||
|
Helper.setViewVisibility(fromPrefix, currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ? View.VISIBLE : View.GONE);
|
||||||
|
Helper.setViewVisibility(contentFromLink, currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ? View.VISIBLE : View.GONE);
|
||||||
|
currentContentFrom = currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ?
|
||||||
|
(currentContentFrom == 0 ? ContentFromDialogFragment.ITEM_FROM_PAST_WEEK : currentContentFrom) : 0;
|
||||||
|
|
||||||
|
updateSortByLinkText();
|
||||||
|
contentSortOrder = Helper.buildContentSortOrder(currentSortBy);
|
||||||
|
contentReleaseTime = Helper.buildReleaseTime(currentContentFrom);
|
||||||
|
fetchClaimSearchContent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSortByLinkText() {
|
||||||
|
int stringResourceId = -1;
|
||||||
|
switch (currentSortBy) {
|
||||||
|
case ContentSortDialogFragment.ITEM_SORT_BY_NEW: default: stringResourceId = R.string.new_text; break;
|
||||||
|
case ContentSortDialogFragment.ITEM_SORT_BY_TOP: stringResourceId = R.string.top; break;
|
||||||
|
case ContentSortDialogFragment.ITEM_SORT_BY_TRENDING: stringResourceId = R.string.trending; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Helper.setViewText(sortLinkText, stringResourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateContentFromLinkText() {
|
||||||
|
int stringResourceId = -1;
|
||||||
|
switch (currentContentFrom) {
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_24_HOURS: stringResourceId = R.string.past_24_hours; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_WEEK: default: stringResourceId = R.string.past_week; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_MONTH: stringResourceId = R.string.past_month; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_YEAR: stringResourceId = R.string.past_year; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_ALL_TIME: stringResourceId = R.string.all_time; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Helper.setViewText(contentFromLinkText, stringResourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
fetchClaimSearchContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildContentOptions() {
|
||||||
|
return Lbry.buildClaimSearchOptions(
|
||||||
|
Claim.TYPE_STREAM,
|
||||||
|
tags != null ? tags : null,
|
||||||
|
null, // TODO: Check mature
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
getContentSortOrder(),
|
||||||
|
contentReleaseTime,
|
||||||
|
currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage,
|
||||||
|
Helper.CONTENT_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getContentSortOrder() {
|
||||||
|
if (contentSortOrder == null) {
|
||||||
|
return Arrays.asList(Claim.ORDER_BY_TRENDING_GROUP, Claim.ORDER_BY_TRENDING_MIXED);
|
||||||
|
}
|
||||||
|
return contentSortOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View getLoadingView() {
|
||||||
|
return (contentListAdapter == null || contentListAdapter.getItemCount() == 0) ? bigContentLoading : contentLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchClaimSearchContent() {
|
||||||
|
fetchClaimSearchContent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchClaimSearchContent(boolean reset) {
|
||||||
|
if (reset && contentListAdapter != null) {
|
||||||
|
contentListAdapter.clearItems();
|
||||||
|
currentClaimSearchPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
contentClaimSearchLoading = true;
|
||||||
|
Map<String, Object> claimSearchOptions = buildContentOptions();
|
||||||
|
contentClaimSearchTask = new ClaimSearchTask(claimSearchOptions, Lbry.LBRY_TV_CONNECTION_STRING, getLoadingView(), new ClaimSearchTask.ClaimSearchResultHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Claim> claims, boolean hasReachedEnd) {
|
||||||
|
if (contentListAdapter == null) {
|
||||||
|
contentListAdapter = new ClaimListAdapter(claims, getContext());
|
||||||
|
contentListAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() {
|
||||||
|
@Override
|
||||||
|
public void onClaimClicked(Claim claim) {
|
||||||
|
String claimId = claim.getClaimId();
|
||||||
|
String url = claim.getPermanentUrl();
|
||||||
|
if (claim.getName().startsWith("@")) {
|
||||||
|
// channel claim
|
||||||
|
Context context = getContext();
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
((MainActivity) context).openChannelClaim(claim);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Intent intent = new Intent(getContext(), FileViewActivity.class);
|
||||||
|
intent.putExtra("claimId", claimId);
|
||||||
|
intent.putExtra("url", url);
|
||||||
|
MainActivity.startingFileViewActivity = true;
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
contentListAdapter.addItems(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentList != null && contentList.getAdapter() == null) {
|
||||||
|
contentList.setAdapter(contentListAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentHasReachedEnd = hasReachedEnd;
|
||||||
|
contentClaimSearchLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception error) {
|
||||||
|
contentClaimSearchLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
contentClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package io.lbry.browser.ui.channel;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.text.HtmlCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import io.lbry.browser.R;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
public class ChannelAboutFragment extends Fragment {
|
||||||
|
private View layoutWebsite;
|
||||||
|
private View layoutEmail;
|
||||||
|
private View layoutInfoArea;
|
||||||
|
private View layoutNoAboutInfo;
|
||||||
|
private TextView textWebsite;
|
||||||
|
private TextView textEmail;
|
||||||
|
private TextView textDescription;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private String website;
|
||||||
|
@Setter
|
||||||
|
private String email;
|
||||||
|
@Setter
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View root = inflater.inflate(R.layout.fragment_channel_about, container, false);
|
||||||
|
|
||||||
|
layoutInfoArea = root.findViewById(R.id.channel_about_info_area);
|
||||||
|
layoutNoAboutInfo = root.findViewById(R.id.channel_about_no_info_container);
|
||||||
|
layoutWebsite = root.findViewById(R.id.channel_about_website_container);
|
||||||
|
layoutEmail = root.findViewById(R.id.channel_about_email_container);
|
||||||
|
textWebsite = root.findViewById(R.id.channel_about_website);
|
||||||
|
textEmail = root.findViewById(R.id.channel_about_email);
|
||||||
|
textDescription = root.findViewById(R.id.channel_about_description);
|
||||||
|
|
||||||
|
boolean noInfo = (Helper.isNullOrEmpty(website) && Helper.isNullOrEmpty(email) && Helper.isNullOrEmpty(description));
|
||||||
|
layoutNoAboutInfo.setVisibility(noInfo ? View.VISIBLE : View.GONE);
|
||||||
|
layoutInfoArea.setVisibility(noInfo ? View.GONE : View.VISIBLE);
|
||||||
|
layoutWebsite.setVisibility(!Helper.isNullOrEmpty(website) ? View.VISIBLE : View.GONE);
|
||||||
|
layoutEmail.setVisibility(!Helper.isNullOrEmpty(email) ? View.VISIBLE : View.GONE);
|
||||||
|
textDescription.setVisibility(!Helper.isNullOrEmpty(description) ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
textWebsite.setLinksClickable(true);
|
||||||
|
textWebsite.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
textWebsite.setText(!Helper.isNullOrEmpty(website) ?
|
||||||
|
HtmlCompat.fromHtml(String.format("<a href=\"%s\">%s</a>", website, website), HtmlCompat.FROM_HTML_MODE_LEGACY) : null);
|
||||||
|
|
||||||
|
textEmail.setText(email);
|
||||||
|
textDescription.setText(description);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
textWebsite.setText(!Helper.isNullOrEmpty(website) ?
|
||||||
|
HtmlCompat.fromHtml(String.format("<a href=\"%s\">%s</a>", website, website), HtmlCompat.FROM_HTML_MODE_LEGACY) : null);
|
||||||
|
|
||||||
|
textEmail.setText(email);
|
||||||
|
textDescription.setText(description);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,273 @@
|
||||||
|
package io.lbry.browser.ui.channel;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.FileViewActivity;
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.browser.R;
|
||||||
|
import io.lbry.browser.adapter.ClaimListAdapter;
|
||||||
|
import io.lbry.browser.dialog.ContentFromDialogFragment;
|
||||||
|
import io.lbry.browser.dialog.ContentSortDialogFragment;
|
||||||
|
import io.lbry.browser.model.Claim;
|
||||||
|
import io.lbry.browser.tasks.ClaimSearchTask;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
public class ChannelContentFragment extends Fragment {
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private String channelId;
|
||||||
|
private View sortLink;
|
||||||
|
private View contentFromLink;
|
||||||
|
private TextView sortLinkText;
|
||||||
|
private TextView contentFromLinkText;
|
||||||
|
private RecyclerView contentList;
|
||||||
|
private int currentSortBy;
|
||||||
|
private int currentContentFrom;
|
||||||
|
private String contentReleaseTime;
|
||||||
|
private List<String> contentSortOrder;
|
||||||
|
private View contentLoading;
|
||||||
|
private View bigContentLoading;
|
||||||
|
private ClaimListAdapter contentListAdapter;
|
||||||
|
private boolean contentClaimSearchLoading;
|
||||||
|
private boolean contentHasReachedEnd;
|
||||||
|
private int currentClaimSearchPage;
|
||||||
|
private ClaimSearchTask contentClaimSearchTask;
|
||||||
|
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View root = inflater.inflate(R.layout.fragment_channel_content, container, false);
|
||||||
|
|
||||||
|
currentSortBy = ContentSortDialogFragment.ITEM_SORT_BY_TRENDING;
|
||||||
|
currentContentFrom = ContentFromDialogFragment.ITEM_FROM_PAST_WEEK;
|
||||||
|
|
||||||
|
sortLink = root.findViewById(R.id.channel_content_sort_link);
|
||||||
|
contentFromLink = root.findViewById(R.id.channel_content_time_link);
|
||||||
|
|
||||||
|
sortLinkText = root.findViewById(R.id.channel_content_sort_link_text);
|
||||||
|
contentFromLinkText = root.findViewById(R.id.channel_content_time_link_text);
|
||||||
|
|
||||||
|
bigContentLoading = root.findViewById(R.id.channel_content_main_progress);
|
||||||
|
contentLoading = root.findViewById(R.id.channel_content_load_progress);
|
||||||
|
|
||||||
|
contentList = root.findViewById(R.id.channel_content_list);
|
||||||
|
LinearLayoutManager llm = new LinearLayoutManager(getContext());
|
||||||
|
contentList.setLayoutManager(llm);
|
||||||
|
contentList.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (contentClaimSearchLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
if (lm != null) {
|
||||||
|
int visibleItemCount = lm.getChildCount();
|
||||||
|
int totalItemCount = lm.getItemCount();
|
||||||
|
int pastVisibleItems = lm.findFirstVisibleItemPosition();
|
||||||
|
if (pastVisibleItems + visibleItemCount >= totalItemCount) {
|
||||||
|
if (!contentHasReachedEnd) {
|
||||||
|
// load more
|
||||||
|
currentClaimSearchPage++;
|
||||||
|
fetchClaimSearchContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sortLink.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
ContentSortDialogFragment dialog = ContentSortDialogFragment.newInstance();
|
||||||
|
dialog.setCurrentSortByItem(currentSortBy);
|
||||||
|
dialog.setSortByListener(new ContentSortDialogFragment.SortByListener() {
|
||||||
|
@Override
|
||||||
|
public void onSortByItemSelected(int sortBy) {
|
||||||
|
onSortByChanged(sortBy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Context context = getContext();
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
MainActivity activity = (MainActivity) context;
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), ContentSortDialogFragment.TAG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
contentFromLink.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
ContentFromDialogFragment dialog = ContentFromDialogFragment.newInstance();
|
||||||
|
dialog.setCurrentFromItem(currentContentFrom);
|
||||||
|
dialog.setContentFromListener(new ContentFromDialogFragment.ContentFromListener() {
|
||||||
|
@Override
|
||||||
|
public void onContentFromItemSelected(int contentFromItem) {
|
||||||
|
onContentFromChanged(contentFromItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Context context = getContext();
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
MainActivity activity = (MainActivity) context;
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), ContentFromDialogFragment.TAG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onContentFromChanged(int contentFrom) {
|
||||||
|
currentContentFrom = contentFrom;
|
||||||
|
|
||||||
|
// rebuild options and search
|
||||||
|
updateContentFromLinkText();
|
||||||
|
contentReleaseTime = Helper.buildReleaseTime(currentContentFrom);
|
||||||
|
fetchClaimSearchContent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSortByChanged(int sortBy) {
|
||||||
|
currentSortBy = sortBy;
|
||||||
|
|
||||||
|
// rebuild options and search
|
||||||
|
Helper.setViewVisibility(contentFromLink, currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ? View.VISIBLE : View.GONE);
|
||||||
|
currentContentFrom = currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ?
|
||||||
|
(currentContentFrom == 0 ? ContentFromDialogFragment.ITEM_FROM_PAST_WEEK : currentContentFrom) : 0;
|
||||||
|
|
||||||
|
updateSortByLinkText();
|
||||||
|
contentSortOrder = Helper.buildContentSortOrder(currentSortBy);
|
||||||
|
contentReleaseTime = Helper.buildReleaseTime(currentContentFrom);
|
||||||
|
fetchClaimSearchContent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSortByLinkText() {
|
||||||
|
int stringResourceId = -1;
|
||||||
|
switch (currentSortBy) {
|
||||||
|
case ContentSortDialogFragment.ITEM_SORT_BY_NEW: default: stringResourceId = R.string.new_text; break;
|
||||||
|
case ContentSortDialogFragment.ITEM_SORT_BY_TOP: stringResourceId = R.string.top; break;
|
||||||
|
case ContentSortDialogFragment.ITEM_SORT_BY_TRENDING: stringResourceId = R.string.trending; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Helper.setViewText(sortLinkText, stringResourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateContentFromLinkText() {
|
||||||
|
int stringResourceId = -1;
|
||||||
|
switch (currentContentFrom) {
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_24_HOURS: stringResourceId = R.string.past_24_hours; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_WEEK: default: stringResourceId = R.string.past_week; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_MONTH: stringResourceId = R.string.past_month; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_YEAR: stringResourceId = R.string.past_year; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_ALL_TIME: stringResourceId = R.string.all_time; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Helper.setViewText(contentFromLinkText, stringResourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
fetchClaimSearchContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
fetchClaimSearchContent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildContentOptions() {
|
||||||
|
return Lbry.buildClaimSearchOptions(
|
||||||
|
Claim.TYPE_STREAM,
|
||||||
|
null,
|
||||||
|
null, // TODO: Check mature
|
||||||
|
Arrays.asList(channelId),
|
||||||
|
null,
|
||||||
|
getContentSortOrder(),
|
||||||
|
contentReleaseTime,
|
||||||
|
currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage,
|
||||||
|
Helper.CONTENT_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getContentSortOrder() {
|
||||||
|
if (contentSortOrder == null) {
|
||||||
|
return Arrays.asList(Claim.ORDER_BY_RELEASE_TIME);
|
||||||
|
}
|
||||||
|
return contentSortOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View getLoadingView() {
|
||||||
|
return (contentListAdapter == null || contentListAdapter.getItemCount() == 0) ? bigContentLoading : contentLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchClaimSearchContent() {
|
||||||
|
fetchClaimSearchContent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchClaimSearchContent(boolean reset) {
|
||||||
|
if (reset && contentListAdapter != null) {
|
||||||
|
contentListAdapter.clearItems();
|
||||||
|
currentClaimSearchPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
contentClaimSearchLoading = true;
|
||||||
|
Map<String, Object> claimSearchOptions = buildContentOptions();
|
||||||
|
contentClaimSearchTask = new ClaimSearchTask(claimSearchOptions, Lbry.LBRY_TV_CONNECTION_STRING, getLoadingView(), new ClaimSearchTask.ClaimSearchResultHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Claim> claims, boolean hasReachedEnd) {
|
||||||
|
if (contentListAdapter == null) {
|
||||||
|
contentListAdapter = new ClaimListAdapter(claims, getContext());
|
||||||
|
contentListAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() {
|
||||||
|
@Override
|
||||||
|
public void onClaimClicked(Claim claim) {
|
||||||
|
String claimId = claim.getClaimId();
|
||||||
|
String url = claim.getPermanentUrl();
|
||||||
|
if (claim.getName().startsWith("@")) {
|
||||||
|
// channel claim
|
||||||
|
Context context = getContext();
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
((MainActivity) context).openChannelClaim(claim);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Intent intent = new Intent(getContext(), FileViewActivity.class);
|
||||||
|
intent.putExtra("claimId", claimId);
|
||||||
|
intent.putExtra("url", url);
|
||||||
|
MainActivity.startingFileViewActivity = true;
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
contentListAdapter.addItems(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentList != null && contentList.getAdapter() == null) {
|
||||||
|
contentList.setAdapter(contentListAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentHasReachedEnd = hasReachedEnd;
|
||||||
|
contentClaimSearchLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception error) {
|
||||||
|
contentClaimSearchLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
contentClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,242 @@
|
||||||
|
package io.lbry.browser.ui.channel;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.browser.R;
|
||||||
|
import io.lbry.browser.model.Claim;
|
||||||
|
import io.lbry.browser.tasks.ResolveTask;
|
||||||
|
import io.lbry.browser.ui.BaseFragment;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
public class ChannelFragment extends BaseFragment {
|
||||||
|
private Claim claim;
|
||||||
|
private boolean resolving;
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
private View layoutResolving;
|
||||||
|
private View layoutDisplayArea;
|
||||||
|
private ImageView imageCover;
|
||||||
|
private ImageView imageThumbnail;
|
||||||
|
private View noThumbnailView;
|
||||||
|
private TextView textAlpha;
|
||||||
|
private TextView textTitle;
|
||||||
|
private TextView textFollowerCount;
|
||||||
|
private TabLayout tabLayout;
|
||||||
|
private ViewPager2 tabPager;
|
||||||
|
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View root = inflater.inflate(R.layout.fragment_channel, container, false);
|
||||||
|
|
||||||
|
layoutDisplayArea = root.findViewById(R.id.channel_view_claim_display_area);
|
||||||
|
layoutResolving = root.findViewById(R.id.channel_view_loading_container);
|
||||||
|
|
||||||
|
imageCover = root.findViewById(R.id.channel_view_cover_image);
|
||||||
|
imageThumbnail = root.findViewById(R.id.channel_view_thumbnail);
|
||||||
|
noThumbnailView = root.findViewById(R.id.channel_view_no_thumbnail);
|
||||||
|
textAlpha = root.findViewById(R.id.channel_view_icon_alpha);
|
||||||
|
textTitle = root.findViewById(R.id.channel_view_title);
|
||||||
|
textFollowerCount = root.findViewById(R.id.channel_view_follower_count);
|
||||||
|
|
||||||
|
tabPager = root.findViewById(R.id.channel_view_pager);
|
||||||
|
tabLayout = root.findViewById(R.id.channel_view_tabs);
|
||||||
|
tabPager.setSaveEnabled(false);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
checkParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkParams() {
|
||||||
|
boolean updateRequired = false;
|
||||||
|
Map<String, Object> params = getParams();
|
||||||
|
|
||||||
|
if (params.containsKey("claim")) {
|
||||||
|
Claim claim = (Claim) params.get("claim");
|
||||||
|
if (claim != null && !claim.equals(this.claim)) {
|
||||||
|
this.claim = claim;
|
||||||
|
updateRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!updateRequired && params.containsKey("url")) {
|
||||||
|
String newUrl = params.get("url").toString();
|
||||||
|
if (!newUrl.equalsIgnoreCase(url) || claim == null) {
|
||||||
|
this.claim = null;
|
||||||
|
this.url = newUrl;
|
||||||
|
updateRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateRequired) {
|
||||||
|
resetFragments();
|
||||||
|
if (!Helper.isNullOrEmpty(url)) {
|
||||||
|
resolveUrl();
|
||||||
|
} else if (claim == null) {
|
||||||
|
// nothing at this location
|
||||||
|
renderNothingAtLocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (claim != null) {
|
||||||
|
renderClaim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveUrl() {
|
||||||
|
layoutDisplayArea.setVisibility(View.INVISIBLE);
|
||||||
|
ResolveTask task = new ResolveTask(url, Lbry.LBRY_TV_CONNECTION_STRING, layoutResolving, new ResolveTask.ResolveResultHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Claim> claims) {
|
||||||
|
if (claims.size() > 0) {
|
||||||
|
claim = claims.get(0);
|
||||||
|
renderClaim();
|
||||||
|
// TODO: Load follower count
|
||||||
|
} else {
|
||||||
|
renderNothingAtLocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception error) {
|
||||||
|
renderNothingAtLocation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderNothingAtLocation() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParams(Map<String, Object> params) {
|
||||||
|
super.setParams(params);
|
||||||
|
if (getView() != null) {
|
||||||
|
checkParams();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderClaim() {
|
||||||
|
if (claim == null) {
|
||||||
|
renderNothingAtLocation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutDisplayArea.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
String thumbnailUrl = claim.getThumbnailUrl();
|
||||||
|
String coverUrl = claim.getCoverUrl();
|
||||||
|
textTitle.setText(Helper.isNullOrEmpty(claim.getTitle()) ? claim.getName() : claim.getTitle());
|
||||||
|
|
||||||
|
if (!Helper.isNullOrEmpty(coverUrl)) {
|
||||||
|
Glide.with(getContext().getApplicationContext()).load(coverUrl).centerCrop().into(imageCover);
|
||||||
|
}
|
||||||
|
if (!Helper.isNullOrEmpty(thumbnailUrl)) {
|
||||||
|
Glide.with(getContext().getApplicationContext()).load(thumbnailUrl).apply(RequestOptions.circleCropTransform()).into(imageThumbnail);
|
||||||
|
noThumbnailView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
imageThumbnail.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
int bgColor = Helper.generateRandomColorForValue(claim.getClaimId());
|
||||||
|
Helper.setIconViewBackgroundColor(noThumbnailView, bgColor, false, getContext());
|
||||||
|
noThumbnailView.setVisibility(View.VISIBLE);
|
||||||
|
textAlpha.setText(claim.getName().substring(1, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
tabPager.setAdapter(new ChannelPagerAdapter(claim, (MainActivity) getContext()));
|
||||||
|
new TabLayoutMediator(tabLayout, tabPager, new TabLayoutMediator.TabConfigurationStrategy() {
|
||||||
|
@Override
|
||||||
|
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
|
||||||
|
tab.setText(position == 0 ? R.string.content : R.string.about);
|
||||||
|
}
|
||||||
|
}).attach();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetFragments() {
|
||||||
|
try {
|
||||||
|
Context context = getContext();
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
MainActivity activity = (MainActivity) getContext();
|
||||||
|
FragmentManager manager = activity.getSupportFragmentManager();
|
||||||
|
FragmentTransaction tx = manager.beginTransaction();
|
||||||
|
for (Fragment fragment : manager.getFragments()) {
|
||||||
|
if (fragment.getClass().equals(ChannelAboutFragment.class) || fragment.getClass().equals(ChannelContentFragment.class)) {
|
||||||
|
tx.remove(fragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.commitAllowingStateLoss();
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ChannelPagerAdapter extends FragmentStateAdapter {
|
||||||
|
private Claim channelClaim;
|
||||||
|
public ChannelPagerAdapter(Claim channelClaim, FragmentActivity activity) {
|
||||||
|
super(activity);
|
||||||
|
this.channelClaim = channelClaim;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
ChannelContentFragment contentFragment = ChannelContentFragment.class.newInstance();
|
||||||
|
contentFragment.setChannelId(channelClaim.getClaimId());
|
||||||
|
return contentFragment;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
ChannelAboutFragment aboutFragment = ChannelAboutFragment.class.newInstance();
|
||||||
|
try {
|
||||||
|
Claim.ChannelMetadata metadata = (Claim.ChannelMetadata) channelClaim.getValue();
|
||||||
|
aboutFragment.setDescription(metadata.getDescription());
|
||||||
|
aboutFragment.setEmail(metadata.getEmail());
|
||||||
|
aboutFragment.setWebsite(metadata.getWebsiteUrl());
|
||||||
|
} catch (ClassCastException ex) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
return aboutFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return String.format("%s-%d", channelClaim.getClaimId(), position).hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package io.lbry.browser.ui.controls;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.Gravity;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView;
|
||||||
|
|
||||||
|
public class SolidIconView extends AppCompatTextView {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public SolidIconView(Context context) {
|
||||||
|
super(context);
|
||||||
|
this.context = context;
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SolidIconView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
this.context = context;
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
setGravity(Gravity.CENTER);
|
||||||
|
setTypeface(Typeface.createFromAsset(context.getAssets(), "font_awesome_5_free_solid.otf"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,581 @@
|
||||||
|
package io.lbry.browser.ui.following;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.android.material.button.MaterialButton;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.lbry.browser.FileViewActivity;
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.browser.R;
|
||||||
|
import io.lbry.browser.adapter.ChannelFilterListAdapter;
|
||||||
|
import io.lbry.browser.adapter.ClaimListAdapter;
|
||||||
|
import io.lbry.browser.adapter.SuggestedChannelGridAdapter;
|
||||||
|
import io.lbry.browser.dialog.ContentFromDialogFragment;
|
||||||
|
import io.lbry.browser.dialog.ContentSortDialogFragment;
|
||||||
|
import io.lbry.browser.exceptions.LbryUriException;
|
||||||
|
import io.lbry.browser.model.Claim;
|
||||||
|
import io.lbry.browser.model.lbryinc.Subscription;
|
||||||
|
import io.lbry.browser.tasks.ChannelSubscribeTask;
|
||||||
|
import io.lbry.browser.tasks.ClaimSearchTask;
|
||||||
|
import io.lbry.browser.tasks.FetchSubscriptionsTask;
|
||||||
|
import io.lbry.browser.tasks.ResolveTask;
|
||||||
|
import io.lbry.browser.listener.ChannelItemSelectionListener;
|
||||||
|
import io.lbry.browser.ui.BaseFragment;
|
||||||
|
import io.lbry.browser.utils.Helper;
|
||||||
|
import io.lbry.browser.utils.Lbry;
|
||||||
|
import io.lbry.browser.utils.LbryUri;
|
||||||
|
import io.lbry.browser.utils.Lbryio;
|
||||||
|
|
||||||
|
public class FollowingFragment extends BaseFragment implements FetchSubscriptionsTask.FetchSubscriptionsHandler, ChannelItemSelectionListener {
|
||||||
|
|
||||||
|
private static final int SUGGESTED_PAGE_SIZE = 45;
|
||||||
|
private static final int MIN_SUGGESTED_SUBSCRIBE_COUNT = 5;
|
||||||
|
|
||||||
|
private MaterialButton suggestedDoneButton;
|
||||||
|
private TextView titleView;
|
||||||
|
private TextView infoView;
|
||||||
|
private RecyclerView horizontalChannelList;
|
||||||
|
private RecyclerView suggestedChannelGrid;
|
||||||
|
private RecyclerView contentList;
|
||||||
|
private ProgressBar bigContentLoading;
|
||||||
|
private ProgressBar contentLoading;
|
||||||
|
private ProgressBar channelListLoading;
|
||||||
|
private View layoutSortContainer;
|
||||||
|
private View sortLink;
|
||||||
|
private TextView sortLinkText;
|
||||||
|
private View contentFromLink;
|
||||||
|
private TextView contentFromLinkText;
|
||||||
|
private int currentSortBy;
|
||||||
|
private int currentContentFrom;
|
||||||
|
private String contentReleaseTime;
|
||||||
|
private List<String> contentSortOrder;
|
||||||
|
private boolean contentClaimSearchLoading = false;
|
||||||
|
|
||||||
|
private List<Integer> queuedContentPages = new ArrayList<>();
|
||||||
|
private List<Integer> queuedSuggestedPages = new ArrayList<>();
|
||||||
|
|
||||||
|
private int currentSuggestedPage = 0;
|
||||||
|
private int currentClaimSearchPage;
|
||||||
|
private boolean suggestedHasReachedEnd;
|
||||||
|
private boolean contentHasReachedEnd;
|
||||||
|
private boolean contentPendingFetch = false;
|
||||||
|
private int numSuggestedSelected;
|
||||||
|
|
||||||
|
// adapters
|
||||||
|
private SuggestedChannelGridAdapter suggestedChannelAdapter;
|
||||||
|
private ChannelFilterListAdapter channelFilterListAdapter;
|
||||||
|
private ClaimListAdapter contentListAdapter;
|
||||||
|
|
||||||
|
private List<String> channelIds;
|
||||||
|
private List<String> channelUrls;
|
||||||
|
private List<Subscription> subscriptionsList;
|
||||||
|
private List<Claim> suggestedChannels;
|
||||||
|
private ClaimSearchTask suggestedChannelClaimSearchTask;
|
||||||
|
private ClaimSearchTask contentClaimSearchTask;
|
||||||
|
private boolean loadingSuggested;
|
||||||
|
private boolean loadingContent;
|
||||||
|
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View root = inflater.inflate(R.layout.fragment_following, container, false);
|
||||||
|
|
||||||
|
// Following page is sorted by new by default, past week if sort is top
|
||||||
|
currentSortBy = ContentSortDialogFragment.ITEM_SORT_BY_NEW;
|
||||||
|
currentContentFrom = ContentFromDialogFragment.ITEM_FROM_PAST_WEEK;
|
||||||
|
|
||||||
|
titleView = root.findViewById(R.id.following_page_title);
|
||||||
|
infoView = root.findViewById(R.id.following_page_info);
|
||||||
|
horizontalChannelList = root.findViewById(R.id.following_channel_list);
|
||||||
|
layoutSortContainer = root.findViewById(R.id.following_filter_container);
|
||||||
|
sortLink = root.findViewById(R.id.following_sort_link);
|
||||||
|
sortLinkText = root.findViewById(R.id.following_sort_link_text);
|
||||||
|
contentFromLink = root.findViewById(R.id.following_time_link);
|
||||||
|
contentFromLinkText = root.findViewById(R.id.following_time_link_text);
|
||||||
|
suggestedChannelGrid = root.findViewById(R.id.following_suggested_grid);
|
||||||
|
suggestedDoneButton = root.findViewById(R.id.following_suggested_done_button);
|
||||||
|
contentList = root.findViewById(R.id.following_content_list);
|
||||||
|
bigContentLoading = root.findViewById(R.id.following_main_progress);
|
||||||
|
contentLoading = root.findViewById(R.id.following_content_progress);
|
||||||
|
channelListLoading = root.findViewById(R.id.following_channel_load_progress);
|
||||||
|
|
||||||
|
Context context = getContext();
|
||||||
|
GridLayoutManager glm = new GridLayoutManager(context, 3);
|
||||||
|
suggestedChannelGrid.setLayoutManager(glm);
|
||||||
|
|
||||||
|
LinearLayoutManager cllm = new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false);
|
||||||
|
horizontalChannelList.setLayoutManager(cllm);
|
||||||
|
|
||||||
|
LinearLayoutManager llm = new LinearLayoutManager(context);
|
||||||
|
contentList.setLayoutManager(llm);
|
||||||
|
contentList.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (contentClaimSearchLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
if (lm != null) {
|
||||||
|
int visibleItemCount = lm.getChildCount();
|
||||||
|
int totalItemCount = lm.getItemCount();
|
||||||
|
int pastVisibleItems = lm.findFirstVisibleItemPosition();
|
||||||
|
if (pastVisibleItems + visibleItemCount >= totalItemCount) {
|
||||||
|
if (!contentHasReachedEnd) {
|
||||||
|
// load more
|
||||||
|
currentClaimSearchPage++;
|
||||||
|
fetchClaimSearchContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
suggestedDoneButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
int selected = suggestedChannelAdapter == null ? 0 : suggestedChannelAdapter.getSelectedCount();
|
||||||
|
int remaining = MIN_SUGGESTED_SUBSCRIBE_COUNT - selected;
|
||||||
|
if (remaining == MIN_SUGGESTED_SUBSCRIBE_COUNT) {
|
||||||
|
Snackbar.make(getView(), R.string.select_five_subscriptions, Snackbar.LENGTH_LONG).show();
|
||||||
|
} else {
|
||||||
|
fetchSubscriptions();
|
||||||
|
showSubscribedContent();
|
||||||
|
fetchAndResolveChannelList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sortLink.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
ContentSortDialogFragment dialog = ContentSortDialogFragment.newInstance();
|
||||||
|
dialog.setCurrentSortByItem(currentSortBy);
|
||||||
|
dialog.setSortByListener(new ContentSortDialogFragment.SortByListener() {
|
||||||
|
@Override
|
||||||
|
public void onSortByItemSelected(int sortBy) {
|
||||||
|
onSortByChanged(sortBy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Context context = getContext();
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
MainActivity activity = (MainActivity) context;
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), ContentSortDialogFragment.TAG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
contentFromLink.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
ContentFromDialogFragment dialog = ContentFromDialogFragment.newInstance();
|
||||||
|
dialog.setCurrentFromItem(currentContentFrom);
|
||||||
|
dialog.setContentFromListener(new ContentFromDialogFragment.ContentFromListener() {
|
||||||
|
@Override
|
||||||
|
public void onContentFromItemSelected(int contentFromItem) {
|
||||||
|
onContentFromChanged(contentFromItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Context context = getContext();
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
MainActivity activity = (MainActivity) context;
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), ContentFromDialogFragment.TAG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onContentFromChanged(int contentFrom) {
|
||||||
|
currentContentFrom = contentFrom;
|
||||||
|
|
||||||
|
// rebuild options and search
|
||||||
|
updateContentFromLinkText();
|
||||||
|
contentReleaseTime = Helper.buildReleaseTime(currentContentFrom);
|
||||||
|
fetchClaimSearchContent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSortByChanged(int sortBy) {
|
||||||
|
currentSortBy = sortBy;
|
||||||
|
|
||||||
|
// rebuild options and search
|
||||||
|
Helper.setViewVisibility(contentFromLink, currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ? View.VISIBLE : View.GONE);
|
||||||
|
currentContentFrom = currentSortBy == ContentSortDialogFragment.ITEM_SORT_BY_TOP ?
|
||||||
|
(currentContentFrom == 0 ? ContentFromDialogFragment.ITEM_FROM_PAST_WEEK : currentContentFrom) : 0;
|
||||||
|
|
||||||
|
updateSortByLinkText();
|
||||||
|
contentSortOrder = Helper.buildContentSortOrder(currentSortBy);
|
||||||
|
contentReleaseTime = Helper.buildReleaseTime(currentContentFrom);
|
||||||
|
fetchClaimSearchContent(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSortByLinkText() {
|
||||||
|
int stringResourceId = -1;
|
||||||
|
switch (currentSortBy) {
|
||||||
|
case ContentSortDialogFragment.ITEM_SORT_BY_NEW: default: stringResourceId = R.string.new_text; break;
|
||||||
|
case ContentSortDialogFragment.ITEM_SORT_BY_TOP: stringResourceId = R.string.top; break;
|
||||||
|
case ContentSortDialogFragment.ITEM_SORT_BY_TRENDING: stringResourceId = R.string.trending; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Helper.setViewText(sortLinkText, stringResourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateContentFromLinkText() {
|
||||||
|
int stringResourceId = -1;
|
||||||
|
switch (currentContentFrom) {
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_24_HOURS: stringResourceId = R.string.past_24_hours; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_WEEK: default: stringResourceId = R.string.past_week; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_MONTH: stringResourceId = R.string.past_month; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_PAST_YEAR: stringResourceId = R.string.past_year; break;
|
||||||
|
case ContentFromDialogFragment.ITEM_FROM_ALL_TIME: stringResourceId = R.string.all_time; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Helper.setViewText(contentFromLinkText, stringResourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
// check if subscriptions exist
|
||||||
|
if (suggestedChannelAdapter != null) {
|
||||||
|
showSuggestedChannels();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Lbryio.cacheSubscriptions != null && Lbryio.cacheSubscriptions.size() > 0) {
|
||||||
|
subscriptionsList = new ArrayList<>(Lbryio.cacheSubscriptions);
|
||||||
|
buildChannelIdsAndUrls();
|
||||||
|
if (Lbryio.cacheResolvedSubscriptions.size() > 0) {
|
||||||
|
updateChannelFilterListAdapter(Lbryio.cacheResolvedSubscriptions);
|
||||||
|
} else {
|
||||||
|
fetchAndResolveChannelList();
|
||||||
|
}
|
||||||
|
fetchClaimSearchContent();
|
||||||
|
showSubscribedContent();
|
||||||
|
} else {
|
||||||
|
fetchSubscriptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadFollowing() {
|
||||||
|
// wrapper to just re-fetch subscriptions (upon user sign in, for example)
|
||||||
|
fetchSubscriptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchSubscriptions() {
|
||||||
|
FetchSubscriptionsTask task = new FetchSubscriptionsTask(getContext(), channelListLoading, this);
|
||||||
|
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildSuggestedOptions() {
|
||||||
|
return Lbry.buildClaimSearchOptions(
|
||||||
|
Claim.TYPE_CHANNEL,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Arrays.asList(Claim.ORDER_BY_EFFECTIVE_AMOUNT),
|
||||||
|
null,
|
||||||
|
currentSuggestedPage == 0 ? 1 : currentSuggestedPage,
|
||||||
|
SUGGESTED_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildContentOptions() {
|
||||||
|
return Lbry.buildClaimSearchOptions(
|
||||||
|
Claim.TYPE_STREAM,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
getChannelIds(),
|
||||||
|
null,
|
||||||
|
getContentSortOrder(),
|
||||||
|
contentReleaseTime,
|
||||||
|
currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage,
|
||||||
|
Helper.CONTENT_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getChannelIds() {
|
||||||
|
if (channelFilterListAdapter != null) {
|
||||||
|
Claim selected = channelFilterListAdapter.getSelectedItem();
|
||||||
|
if (selected != null) {
|
||||||
|
return Arrays.asList(selected.getClaimId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return channelIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getContentSortOrder() {
|
||||||
|
if (contentSortOrder == null) {
|
||||||
|
return Arrays.asList(Claim.ORDER_BY_RELEASE_TIME);
|
||||||
|
}
|
||||||
|
return contentSortOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSuggestedChannels() {
|
||||||
|
Helper.setViewText(titleView, R.string.find_channels_to_follow);
|
||||||
|
|
||||||
|
Helper.setViewVisibility(horizontalChannelList, View.GONE);
|
||||||
|
Helper.setViewVisibility(contentList, View.GONE);
|
||||||
|
Helper.setViewVisibility(infoView, View.VISIBLE);
|
||||||
|
Helper.setViewVisibility(layoutSortContainer, View.GONE);
|
||||||
|
Helper.setViewVisibility(suggestedChannelGrid, View.VISIBLE);
|
||||||
|
Helper.setViewVisibility(suggestedDoneButton, View.VISIBLE);
|
||||||
|
|
||||||
|
updateSuggestedDoneButtonText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSubscribedContent() {
|
||||||
|
Helper.setViewText(titleView, R.string.channels_you_follow);
|
||||||
|
|
||||||
|
Helper.setViewVisibility(horizontalChannelList, View.VISIBLE);
|
||||||
|
Helper.setViewVisibility(contentList, View.VISIBLE);
|
||||||
|
Helper.setViewVisibility(infoView, View.GONE);
|
||||||
|
Helper.setViewVisibility(layoutSortContainer, View.VISIBLE);
|
||||||
|
Helper.setViewVisibility(suggestedChannelGrid, View.GONE);
|
||||||
|
Helper.setViewVisibility(suggestedDoneButton, View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildChannelIdsAndUrls() {
|
||||||
|
channelIds = new ArrayList<>();
|
||||||
|
channelUrls = new ArrayList<>();
|
||||||
|
if (subscriptionsList != null) {
|
||||||
|
for (Subscription subscription : subscriptionsList) {
|
||||||
|
try {
|
||||||
|
String url = subscription.getUrl();
|
||||||
|
LbryUri uri = LbryUri.parse(url);
|
||||||
|
String claimId = uri.getClaimId();
|
||||||
|
channelIds.add(claimId);
|
||||||
|
channelUrls.add(url);
|
||||||
|
} catch (LbryUriException ex) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchAndResolveChannelList() {
|
||||||
|
buildChannelIdsAndUrls();
|
||||||
|
if (channelIds.size() > 0) {
|
||||||
|
ResolveTask resolveSubscribedTask = new ResolveTask(channelUrls, Lbry.LBRY_TV_CONNECTION_STRING, channelListLoading, new ResolveTask.ResolveResultHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Claim> claims) {
|
||||||
|
updateChannelFilterListAdapter(claims);
|
||||||
|
Lbryio.cacheResolvedSubscriptions = claims;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception error) {
|
||||||
|
fetchAndResolveChannelList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resolveSubscribedTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
|
||||||
|
fetchClaimSearchContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private View getLoadingView() {
|
||||||
|
return (contentListAdapter == null || contentListAdapter.getItemCount() == 0) ? bigContentLoading : contentLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateChannelFilterListAdapter(List<Claim> resolvedSubs) {
|
||||||
|
if (channelFilterListAdapter == null) {
|
||||||
|
channelFilterListAdapter = new ChannelFilterListAdapter(getContext());
|
||||||
|
channelFilterListAdapter.setListener(new ChannelItemSelectionListener() {
|
||||||
|
@Override
|
||||||
|
public void onChannelItemSelected(Claim claim) {
|
||||||
|
if (contentClaimSearchTask != null && contentClaimSearchTask.getStatus() != AsyncTask.Status.FINISHED) {
|
||||||
|
contentClaimSearchTask.cancel(true);
|
||||||
|
}
|
||||||
|
if (contentListAdapter != null) {
|
||||||
|
contentListAdapter.clearItems();
|
||||||
|
}
|
||||||
|
currentClaimSearchPage = 1;
|
||||||
|
contentClaimSearchLoading = false;
|
||||||
|
fetchClaimSearchContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChannelItemDeselected(Claim claim) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChannelSelectionCleared() {
|
||||||
|
if (contentClaimSearchTask != null && contentClaimSearchTask.getStatus() != AsyncTask.Status.FINISHED) {
|
||||||
|
contentClaimSearchTask.cancel(true);
|
||||||
|
}
|
||||||
|
if (contentListAdapter != null) {
|
||||||
|
contentListAdapter.clearItems();
|
||||||
|
}
|
||||||
|
currentClaimSearchPage = 1;
|
||||||
|
contentClaimSearchLoading = false;
|
||||||
|
fetchClaimSearchContent();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (horizontalChannelList != null && horizontalChannelList.getAdapter() == null) {
|
||||||
|
horizontalChannelList.setAdapter(channelFilterListAdapter);
|
||||||
|
}
|
||||||
|
channelFilterListAdapter.addClaims(resolvedSubs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchClaimSearchContent() {
|
||||||
|
fetchClaimSearchContent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchClaimSearchContent(boolean reset) {
|
||||||
|
if (reset && contentListAdapter != null) {
|
||||||
|
contentListAdapter.clearItems();
|
||||||
|
currentClaimSearchPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
contentClaimSearchLoading = true;
|
||||||
|
Map<String, Object> claimSearchOptions = buildContentOptions();
|
||||||
|
contentClaimSearchTask = new ClaimSearchTask(claimSearchOptions, Lbry.LBRY_TV_CONNECTION_STRING, getLoadingView(), new ClaimSearchTask.ClaimSearchResultHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Claim> claims, boolean hasReachedEnd) {
|
||||||
|
if (contentListAdapter == null) {
|
||||||
|
contentListAdapter = new ClaimListAdapter(claims, getContext());
|
||||||
|
contentListAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() {
|
||||||
|
@Override
|
||||||
|
public void onClaimClicked(Claim claim) {
|
||||||
|
String claimId = claim.getClaimId();
|
||||||
|
String url = claim.getPermanentUrl();
|
||||||
|
|
||||||
|
if (claim.getName().startsWith("@")) {
|
||||||
|
// channel claim
|
||||||
|
Context context = getContext();
|
||||||
|
if (context instanceof MainActivity) {
|
||||||
|
((MainActivity) context).openChannelClaim(claim);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Intent intent = new Intent(getContext(), FileViewActivity.class);
|
||||||
|
intent.putExtra("claimId", claimId);
|
||||||
|
intent.putExtra("url", url);
|
||||||
|
MainActivity.startingFileViewActivity = true;
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
contentListAdapter.addItems(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentList != null && contentList.getAdapter() == null) {
|
||||||
|
contentList.setAdapter(contentListAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentHasReachedEnd = hasReachedEnd;
|
||||||
|
contentClaimSearchLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception error) {
|
||||||
|
contentClaimSearchLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
contentClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSuggestedDoneButtonText() {
|
||||||
|
int selected = suggestedChannelAdapter == null ? 0 : suggestedChannelAdapter.getSelectedCount();
|
||||||
|
int remaining = MIN_SUGGESTED_SUBSCRIBE_COUNT - selected;
|
||||||
|
String buttonText = remaining <= 0 ? getString(R.string.done) : getString(R.string.n_remaining, remaining);
|
||||||
|
if (suggestedDoneButton != null) {
|
||||||
|
suggestedDoneButton.setText(buttonText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handler methods
|
||||||
|
public void onSuccess(List<Subscription> subscriptions) {
|
||||||
|
if (subscriptions.size() == 0) {
|
||||||
|
// fresh start
|
||||||
|
// TODO: Only do this if there are no local subscriptions stored
|
||||||
|
currentSuggestedPage = 1;
|
||||||
|
buildSuggestedOptions();
|
||||||
|
loadingSuggested = true;
|
||||||
|
loadingContent = false;
|
||||||
|
|
||||||
|
suggestedChannelClaimSearchTask = new ClaimSearchTask(
|
||||||
|
buildSuggestedOptions(),
|
||||||
|
Lbry.LBRY_TV_CONNECTION_STRING,
|
||||||
|
suggestedChannelAdapter == null ? bigContentLoading : contentLoading,
|
||||||
|
new ClaimSearchTask.ClaimSearchResultHandler() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Claim> claims, boolean hasReachedEnd) {
|
||||||
|
if (suggestedChannelAdapter == null) {
|
||||||
|
suggestedChannelAdapter = new SuggestedChannelGridAdapter(claims, getContext());
|
||||||
|
suggestedChannelAdapter.setListener(FollowingFragment.this);
|
||||||
|
if (suggestedChannelGrid != null) {
|
||||||
|
suggestedChannelGrid.setAdapter(suggestedChannelAdapter);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
suggestedChannelAdapter.addClaims(claims);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
suggestedChannelClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
showSuggestedChannels();
|
||||||
|
} else {
|
||||||
|
Lbryio.cacheSubscriptions = subscriptions;
|
||||||
|
subscriptionsList = new ArrayList<>(subscriptions);
|
||||||
|
showSubscribedContent();
|
||||||
|
fetchAndResolveChannelList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onError(Exception exception) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onChannelItemSelected(Claim claim) {
|
||||||
|
// subscribe
|
||||||
|
Subscription subscription = new Subscription();
|
||||||
|
subscription.setChannelName(claim.getName());
|
||||||
|
subscription.setUrl(claim.getPermanentUrl());
|
||||||
|
String channelClaimId = claim.getClaimId();
|
||||||
|
|
||||||
|
ChannelSubscribeTask task = new ChannelSubscribeTask(getContext(), channelClaimId, subscription, false, null);
|
||||||
|
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
updateSuggestedDoneButtonText();
|
||||||
|
}
|
||||||
|
public void onChannelItemDeselected(Claim claim) {
|
||||||
|
// unsubscribe
|
||||||
|
Subscription subscription = new Subscription();
|
||||||
|
subscription.setChannelName(claim.getName());
|
||||||
|
subscription.setUrl(claim.getPermanentUrl());
|
||||||
|
String channelClaimId = claim.getClaimId();
|
||||||
|
|
||||||
|
ChannelSubscribeTask task = new ChannelSubscribeTask(getContext(), channelClaimId, subscription, true, null);
|
||||||
|
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
updateSuggestedDoneButtonText();
|
||||||
|
}
|
||||||
|
public void onChannelSelectionCleared() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue