diff --git a/.gitignore b/.gitignore
index c21e214d..22ac5d86 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,6 +60,7 @@ buck-out/
# Other Files
app/google-services.json
+app/twitter.properties
*.log
.vagrant
*.hprof
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 33fca8b4..310c930a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -11,7 +11,7 @@ build apk:
- apt-get -y update && apt-get -y install build-essential ca-certificates curl git gpg-agent openjdk-8-jdk software-properties-common wget zipalign
- chmod u+x $CI_PROJECT_DIR/gradlew
- export ANDROID_SDK_ROOT=~/.buildozer/android/platform/android-sdk-23
- - export BUILD_VERSION=$($CI_PROJECT_DIR/gradlew -q printVersionName --console=plain | tail -1)
+ - export BUILD_VERSION=$($CI_PROJECT_DIR/gradlew -p $CI_PROJECT_DIR -q printVersionName --console=plain | tail -1)
artifacts:
paths:
- bin/browser-*-release__arm.apk
@@ -27,8 +27,8 @@ build apk:
- yarn
- chmod u+x ./release.sh
- ./release.sh
- - cp bin/browser-$BUILD_VERSION-release__arm.apk /dev/null
- - cp bin/browser-$BUILD_VERSION-release__arm64.apk /dev/null
+ - cp bin/browser-$BUILD_VERSION-release__arm.apk $CI_PROJECT_DIR
+ - cp bin/browser-$BUILD_VERSION-release__arm64.apk $CI_PROJECT_DIR
deploy build.lbry.io:
image: python:stretch
@@ -39,7 +39,7 @@ deploy build.lbry.io:
- apt-get -y update && apt-get -y install openjdk-8-jdk
- pip install awscli
- chmod u+x $CI_PROJECT_DIR/gradlew
- - export BUILD_VERSION=$($CI_PROJECT_DIR/gradlew -q printVersionName --console=plain | tail -1)
+ - export BUILD_VERSION=$($CI_PROJECT_DIR/gradlew -p $CI_PROJECT_DIR -q printVersionName --console=plain | tail -1)
- export BUILD_APK_FILENAME__32=browser-$BUILD_VERSION-release__arm.apk
- export BUILD_APK_FILENAME__64=browser-$BUILD_VERSION-release__arm64.apk
script:
@@ -58,7 +58,7 @@ release apk:
- apt-get -y update && apt-get -y install openjdk-8-jdk
- pip install awscli githubrelease
- chmod u+x $CI_PROJECT_DIR/gradlew
- - export BUILD_VERSION=$($CI_PROJECT_DIR/gradlew -q printVersionName --console=plain | tail -1)
+ - export BUILD_VERSION=$($CI_PROJECT_DIR/gradlew -p $CI_PROJECT_DIR -q printVersionName --console=plain | tail -1)
- export BUILD_APK_FILENAME__32=browser-$BUILD_VERSION-release__arm.apk
- export BUILD_APK_FILENAME__64=browser-$BUILD_VERSION-release__arm64.apk
script:
diff --git a/.gitsecret/keys/pubring.kbx b/.gitsecret/keys/pubring.kbx
index b5b2e868..e16cdd1b 100644
Binary files a/.gitsecret/keys/pubring.kbx and b/.gitsecret/keys/pubring.kbx differ
diff --git a/.gitsecret/keys/pubring.kbx~ b/.gitsecret/keys/pubring.kbx~
index 64baf5a6..b5b2e868 100644
Binary files a/.gitsecret/keys/pubring.kbx~ and b/.gitsecret/keys/pubring.kbx~ differ
diff --git a/.gitsecret/keys/random_seed b/.gitsecret/keys/random_seed
index 86bb2f48..3708c629 100644
Binary files a/.gitsecret/keys/random_seed and b/.gitsecret/keys/random_seed differ
diff --git a/.gitsecret/paths/mapping.cfg b/.gitsecret/paths/mapping.cfg
index 3aff0c5a..5d9c956c 100644
--- a/.gitsecret/paths/mapping.cfg
+++ b/.gitsecret/paths/mapping.cfg
@@ -1,2 +1,3 @@
lbry-android.keystore:0d958c531870694624cc877ea98ca1c583485f8ebbb3a5acca58b1930c190d65
app/google-services.json:896a0bee8294a36d061f10fa926129d8a780528b34d0a2f03113400c4246d67c
+app/twitter.properties:01212d70712f2041efb5c814bf30ecbf6f72e1ca5179c7647c4f8cbd995dd033
diff --git a/app/build.gradle b/app/build.gradle
index 3b8e12de..7a0542a4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,12 @@
import com.google.gms.googleservices.GoogleServicesPlugin
+Properties twitterProps = new Properties()
+try {
+ twitterProps.load(project.file('twitter.properties').newDataInputStream())
+} catch (Exception ex) {
+ throw new GradleException("Missing twitter.properties.")
+}
+
apply plugin: 'com.android.application'
android {
@@ -22,6 +29,10 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
+ packagingOptions {
+ exclude 'META-INF/DEPENDENCIES'
+ }
+
productFlavors {
__32bit {
versionCode android.defaultConfig.versionCode * 10 + 1
@@ -38,7 +49,13 @@ android {
}
buildTypes {
+ debug {
+ resValue "string", "TWITTER_CONSUMER_KEY", "\"${twitterProps.getProperty("twitterConsumerKey")}\""
+ resValue "string", "TWITTER_CONSUMER_SECRET", "\"${twitterProps.getProperty("twitterConsumerSecret")}\""
+ }
release {
+ resValue "string", "TWITTER_CONSUMER_KEY", "\"${twitterProps.getProperty("twitterConsumerKey")}\""
+ resValue "string", "TWITTER_CONSUMER_SECRET", "\"${twitterProps.getProperty("twitterConsumerSecret")}\""
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
@@ -51,6 +68,13 @@ task printVersionName {
}
}
+configurations {
+ all {
+ exclude module: 'httpclient'
+ exclude module: 'commons-logging'
+ }
+}
+
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
@@ -76,6 +100,9 @@ dependencies {
implementation 'com.google.firebase:firebase-analytics:17.4.2'
implementation 'com.google.android.gms:play-services-base:17.2.1'
implementation 'com.google.firebase:firebase-messaging:20.2.0'
+ implementation 'com.google.oauth-client:google-oauth-client:1.30.4'
+
+ implementation 'com.android.billingclient:billing:3.0.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.google.android.exoplayer:exoplayer-core:2.11.4'
diff --git a/app/google-services.json.secret b/app/google-services.json.secret
index 1e09a2e1..ada7f6f6 100644
Binary files a/app/google-services.json.secret and b/app/google-services.json.secret differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f7d535e9..145b4940 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+
specialRouteFragmentClassMap;
@Getter
private boolean inPictureInPictureMode;
@@ -213,7 +224,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
public static boolean startingPermissionRequest = false;
public static boolean startingSignInFlowActivity = false;
-
+ private BillingClient billingClient;
@Getter
private boolean enteringPIPMode = false;
private boolean fullSyncInProgress = false;
@@ -408,6 +419,24 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
+ // setup the billing client in main activity (to handle cases where the verification purchase flow may have been interrupted)
+ billingClient = BillingClient.newBuilder(this)
+ .setListener(new PurchasesUpdatedListener() {
+ @Override
+ public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List purchases) {
+ int responseCode = billingResult.getResponseCode();
+ if (responseCode == BillingClient.BillingResponseCode.OK && purchases != null)
+ {
+ for (Purchase purchase : purchases) {
+ handlePurchase(purchase);
+ }
+ }
+ }
+ })
+ .enablePendingPurchases()
+ .build();
+ establishBillingClientConnection();
+
playerNotificationManager = new PlayerNotificationManager(
this, LbrynetService.NOTIFICATION_CHANNEL_ID, PLAYBACK_NOTIFICATION_ID, new PlayerNotificationDescriptionAdapter());
@@ -1024,6 +1053,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
@Override
protected void onResume() {
super.onResume();
+ checkPurchases();
enteringPIPMode = false;
applyNavbarSigninPadding();
@@ -1046,6 +1076,33 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}*/
}
+ private void checkPurchases() {
+ if (billingClient != null) {
+ Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
+ if (result.getPurchasesList() != null) {
+ for (Purchase purchase : result.getPurchasesList()) {
+ handlePurchase(purchase);
+ }
+ }
+ }
+ }
+
+ private void handlePurchase(Purchase purchase) {
+ handleBillingPurchase(purchase, billingClient, MainActivity.this, null, new RewardVerifiedHandler() {
+ @Override
+ public void onSuccess(RewardVerified rewardVerified) {
+ if (Lbryio.currentUser != null) {
+ Lbryio.currentUser.setRewardApproved(rewardVerified.isRewardApproved());
+ }
+ }
+
+ @Override
+ public void onError(Exception error) {
+ // pass
+ }
+ });
+ }
+
private void checkPendingOpens() {
if (pendingFollowingReload) {
loadFollowingContent();
@@ -2536,8 +2593,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED, true);
}
} catch (Exception ex) {
- // nope
- android.util.Log.e(TAG, String.format("App startup failed: %s", ex.getMessage()), ex);
+ // nopecd
+ Log.e(TAG, String.format("App startup failed: %s", ex.getMessage()), ex);
return false;
} finally {
Helper.closeCloseable(reader);
@@ -3269,6 +3326,53 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
return (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED);
}
+ private void establishBillingClientConnection() {
+ if (billingClient != null) {
+ billingClient.startConnection(new BillingClientStateListener() {
+ @Override
+ public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
+ if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
+ // no need to do anything here. purchases are always checked server-side
+ checkPurchases();
+ }
+ }
+
+ @Override
+ public void onBillingServiceDisconnected() {
+ establishBillingClientConnection();
+ }
+ });
+ }
+ }
+
+ public static void handleBillingPurchase(
+ Purchase purchase,
+ BillingClient billingClient,
+ Context context,
+ View progressView,
+ RewardVerifiedHandler handler) {
+ String sku = purchase.getSku();
+ if (SKU_SKIP.equalsIgnoreCase(sku)) {
+ // send purchase token for verification
+ if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
+ /*&& isSignatureValid(purchase)*/) {
+ // consume the purchase
+ String purchaseToken = purchase.getPurchaseToken();
+ ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build();
+ billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() {
+ @Override
+ public void onConsumeResponse(@NonNull BillingResult billingResult, @NonNull String s) {
+
+ }
+ });
+
+ // send the purchase token to the backend to complete verification
+ AndroidPurchaseTask task = new AndroidPurchaseTask(purchaseToken, progressView, context, handler);
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+ }
+ }
+
public interface BackPressInterceptor {
boolean onBackPressed();
}
diff --git a/app/src/main/java/io/lbry/browser/VerificationActivity.java b/app/src/main/java/io/lbry/browser/VerificationActivity.java
index f332b25d..2950dd3b 100644
--- a/app/src/main/java/io/lbry/browser/VerificationActivity.java
+++ b/app/src/main/java/io/lbry/browser/VerificationActivity.java
@@ -7,19 +7,35 @@ import android.content.IntentFilter;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.widget.ViewPager2;
+import com.android.billingclient.api.BillingClient;
+import com.android.billingclient.api.BillingClientStateListener;
+import com.android.billingclient.api.BillingFlowParams;
+import com.android.billingclient.api.BillingResult;
+import com.android.billingclient.api.Purchase;
+import com.android.billingclient.api.PurchasesUpdatedListener;
+import com.android.billingclient.api.SkuDetails;
+import com.android.billingclient.api.SkuDetailsParams;
+import com.android.billingclient.api.SkuDetailsResponseListener;
import com.google.android.material.snackbar.Snackbar;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import io.lbry.browser.adapter.VerificationPagerAdapter;
import io.lbry.browser.listener.SignInListener;
import io.lbry.browser.listener.WalletSyncListener;
+import io.lbry.browser.model.lbryinc.RewardVerified;
import io.lbry.browser.model.lbryinc.User;
+import io.lbry.browser.tasks.RewardVerifiedHandler;
import io.lbry.browser.tasks.lbryinc.FetchCurrentUserTask;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.LbryAnalytics;
@@ -32,11 +48,59 @@ public class VerificationActivity extends FragmentActivity implements SignInList
public static final int VERIFICATION_FLOW_REWARDS = 2;
public static final int VERIFICATION_FLOW_WALLET = 3;
+ private BillingClient billingClient;
private BroadcastReceiver sdkReceiver;
private String email;
private boolean signedIn;
private int flow;
+ private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
+ @Override
+ public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List purchases) {
+ int responseCode = billingResult.getResponseCode();
+ if (responseCode == BillingClient.BillingResponseCode.OK && purchases != null)
+ {
+ for (Purchase purchase : purchases) {
+ if (MainActivity.SKU_SKIP.equalsIgnoreCase(purchase.getSku())) {
+ showLoading();
+ MainActivity.handleBillingPurchase(
+ purchase,
+ billingClient,
+ VerificationActivity.this, null, new RewardVerifiedHandler() {
+ @Override
+ public void onSuccess(RewardVerified rewardVerified) {
+ if (Lbryio.currentUser != null) {
+ Lbryio.currentUser.setRewardApproved(rewardVerified.isRewardApproved());
+ }
+
+ if (!rewardVerified.isRewardApproved()) {
+ // show pending purchase message (possible slow card tx)
+ Snackbar.make(findViewById(R.id.verification_pager), R.string.purchase_request_pending, Snackbar.LENGTH_LONG).show();
+ } else {
+ Snackbar.make(findViewById(R.id.verification_pager), R.string.reward_verification_successful, Snackbar.LENGTH_LONG).show();
+ }
+
+ setResult(RESULT_OK);
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ }, 3000);
+ }
+
+ @Override
+ public void onError(Exception error) {
+ showFetchUserError(getString(R.string.purchase_request_failed_error));
+ hideLoading();
+ }
+ });
+ }
+ }
+ }
+ }
+ };
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -73,6 +137,12 @@ public class VerificationActivity extends FragmentActivity implements SignInList
};
registerReceiver(sdkReceiver, filter);
+ billingClient = BillingClient.newBuilder(this)
+ .setListener(purchasesUpdatedListener)
+ .enablePendingPurchases()
+ .build();
+ establishBillingClientConnection();
+
setContentView(R.layout.activity_verification);
ViewPager2 viewPager = findViewById(R.id.verification_pager);
viewPager.setUserInputEnabled(false);
@@ -89,6 +159,24 @@ public class VerificationActivity extends FragmentActivity implements SignInList
});
}
+ private void establishBillingClientConnection() {
+ if (billingClient != null) {
+ billingClient.startConnection(new BillingClientStateListener() {
+ @Override
+ public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
+ if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
+ // no need to do anything here. purchases are always checked server-side
+ }
+ }
+
+ @Override
+ public void onBillingServiceDisconnected() {
+ establishBillingClientConnection();
+ }
+ });
+ }
+ }
+
public void onResume() {
super.onResume();
LbryAnalytics.setCurrentScreen(this, "Verification", "Verification");
@@ -104,11 +192,13 @@ public class VerificationActivity extends FragmentActivity implements SignInList
flowHandled = true;
} else if (flow == VERIFICATION_FLOW_REWARDS) {
User user = Lbryio.currentUser;
- if (!user.isIdentityVerified()) {
+ // disable phone verification for now
+ /*if (!user.isIdentityVerified()) {
// phone number verification required
viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_PHONE, false);
flowHandled = true;
- } else if (!user.isRewardApproved()) {
+ } else */
+ if (!user.isRewardApproved()) {
// manual verification required
viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_MANUAL, false);
flowHandled = true;
@@ -195,10 +285,14 @@ public class VerificationActivity extends FragmentActivity implements SignInList
ViewPager2 viewPager = findViewById(R.id.verification_pager);
// for rewards, (show phone verification if not done, or manual verification if required)
if (flow == VERIFICATION_FLOW_REWARDS) {
- if (!user.isIdentityVerified()) {
+ // skipping phone verification
+ /*if (!user.isIdentityVerified()) {
// phone number verification required
viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_PHONE, false);
- } else if (!user.isRewardApproved()) {
+ } else
+ */
+ if (!user.isRewardApproved()) {
+
// manual verification required
viewPager.setCurrentItem(VerificationPagerAdapter.PAGE_VERIFICATION_MANUAL, false);
} else {
@@ -289,6 +383,54 @@ public class VerificationActivity extends FragmentActivity implements SignInList
findViewById(R.id.verification_close_button).setVisibility(View.VISIBLE);
}
+ @Override
+ public void onSkipQueueAction() {
+ if (billingClient != null) {
+ List skuList = new ArrayList<>();
+ skuList.add(MainActivity.SKU_SKIP);
+
+ SkuDetailsParams detailsParams = SkuDetailsParams.newBuilder().
+ setType(BillingClient.SkuType.INAPP).
+ setSkusList(skuList).build();
+ billingClient.querySkuDetailsAsync(detailsParams, new SkuDetailsResponseListener() {
+ @Override
+ public void onSkuDetailsResponse(@NonNull BillingResult billingResult, @Nullable List list) {
+ if (list != null && list.size() > 0) {
+ // we only queried one product, so it should be the first item in the list
+ SkuDetails skuDetails = list.get(0);
+
+ // launch the billing flow for skip queue
+ BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().
+ setSkuDetails(skuDetails).build();
+ billingClient.launchBillingFlow(VerificationActivity.this, billingFlowParams);
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onTwitterVerified() {
+ Snackbar.make(findViewById(R.id.verification_pager), R.string.reward_verification_successful, Snackbar.LENGTH_LONG).show();
+
+ setResult(RESULT_OK);
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ }, 3000);
+ }
+
+ @Override
+ public void onManualProgress(boolean progress) {
+ if (progress) {
+ findViewById(R.id.verification_close_button).setVisibility(View.GONE);
+ } else {
+ findViewById(R.id.verification_close_button).setVisibility(View.VISIBLE);
+ }
+ }
+
@Override
public void onDestroy() {
Helper.unregisterReceiver(sdkReceiver, this);
diff --git a/app/src/main/java/io/lbry/browser/listener/SignInListener.java b/app/src/main/java/io/lbry/browser/listener/SignInListener.java
index 866e2774..74d8ddf9 100644
--- a/app/src/main/java/io/lbry/browser/listener/SignInListener.java
+++ b/app/src/main/java/io/lbry/browser/listener/SignInListener.java
@@ -7,4 +7,7 @@ public interface SignInListener {
void onPhoneAdded(String countryCode, String phoneNumber);
void onPhoneVerified();
void onManualVerifyContinue();
+ void onSkipQueueAction();
+ void onTwitterVerified();
+ void onManualProgress(boolean progress);
}
diff --git a/app/src/main/java/io/lbry/browser/model/TwitterOauth.java b/app/src/main/java/io/lbry/browser/model/TwitterOauth.java
new file mode 100644
index 00000000..9d0bc847
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/TwitterOauth.java
@@ -0,0 +1,9 @@
+package io.lbry.browser.model;
+
+import lombok.Data;
+
+@Data
+public class TwitterOauth {
+ private String oauthToken;
+ private String oauthTokenSecret;
+}
diff --git a/app/src/main/java/io/lbry/browser/model/lbryinc/RewardVerified.java b/app/src/main/java/io/lbry/browser/model/lbryinc/RewardVerified.java
new file mode 100644
index 00000000..88713110
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/model/lbryinc/RewardVerified.java
@@ -0,0 +1,9 @@
+package io.lbry.browser.model.lbryinc;
+
+import lombok.Data;
+
+@Data
+public class RewardVerified {
+ private long userId;
+ private boolean isRewardApproved;
+}
diff --git a/app/src/main/java/io/lbry/browser/tasks/RewardVerifiedHandler.java b/app/src/main/java/io/lbry/browser/tasks/RewardVerifiedHandler.java
new file mode 100644
index 00000000..4c78fee7
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/tasks/RewardVerifiedHandler.java
@@ -0,0 +1,8 @@
+package io.lbry.browser.tasks;
+
+import io.lbry.browser.model.lbryinc.RewardVerified;
+
+public interface RewardVerifiedHandler {
+ void onSuccess(RewardVerified rewardVerified);
+ void onError(Exception error);
+}
diff --git a/app/src/main/java/io/lbry/browser/tasks/TwitterOauthHandler.java b/app/src/main/java/io/lbry/browser/tasks/TwitterOauthHandler.java
new file mode 100644
index 00000000..49357ced
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/tasks/TwitterOauthHandler.java
@@ -0,0 +1,8 @@
+package io.lbry.browser.tasks;
+
+import io.lbry.browser.model.TwitterOauth;
+
+public interface TwitterOauthHandler {
+ void onSuccess(TwitterOauth twitterOauth);
+ void onError(Exception error);
+}
diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/AndroidPurchaseTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/AndroidPurchaseTask.java
new file mode 100644
index 00000000..67a2ba8e
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/AndroidPurchaseTask.java
@@ -0,0 +1,68 @@
+package io.lbry.browser.tasks.lbryinc;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.view.View;
+
+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 java.util.HashMap;
+import java.util.Map;
+
+import io.lbry.browser.MainActivity;
+import io.lbry.browser.model.lbryinc.RewardVerified;
+import io.lbry.browser.tasks.RewardVerifiedHandler;
+import io.lbry.browser.utils.Helper;
+import io.lbry.browser.utils.Lbryio;
+import okhttp3.Response;
+
+public class AndroidPurchaseTask extends AsyncTask {
+ private Context context;
+ private View progressView;
+ private String purchaseToken;
+ private RewardVerifiedHandler handler;
+ private Exception error;
+
+ public AndroidPurchaseTask(String purchaseToken, View progressView, Context context, RewardVerifiedHandler handler) {
+ this.purchaseToken = purchaseToken;
+ this.progressView = progressView;
+ this.context = context;
+ this.handler = handler;
+ }
+
+ protected void onPreExecute() {
+ Helper.setViewVisibility(progressView, View.VISIBLE);
+ }
+
+ protected RewardVerified doInBackground(Void... params) {
+ try {
+ Map options = new HashMap<>();
+ options.put("purchase_token", purchaseToken);
+
+ JSONObject object = (JSONObject) Lbryio.parseResponse(Lbryio.call("verification", "android_purchase", options, context));
+ Type type = new TypeToken(){}.getType();
+ Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+ return gson.fromJson(object.toString(), type);
+ } catch (Exception ex) {
+ error = ex;
+ return null;
+ }
+ }
+
+ protected void onPostExecute(RewardVerified result) {
+ Helper.setViewVisibility(progressView, View.GONE);
+ if (handler != null) {
+ if (result != null) {
+ handler.onSuccess(result);
+ } else {
+ handler.onError(error);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/io/lbry/browser/tasks/lbryinc/TwitterVerifyTask.java b/app/src/main/java/io/lbry/browser/tasks/lbryinc/TwitterVerifyTask.java
new file mode 100644
index 00000000..265125e3
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/tasks/lbryinc/TwitterVerifyTask.java
@@ -0,0 +1,68 @@
+package io.lbry.browser.tasks.lbryinc;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.view.View;
+
+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 java.util.HashMap;
+import java.util.Map;
+
+import io.lbry.browser.model.TwitterOauth;
+import io.lbry.browser.model.lbryinc.RewardVerified;
+import io.lbry.browser.tasks.RewardVerifiedHandler;
+import io.lbry.browser.utils.Helper;
+import io.lbry.browser.utils.Lbryio;
+
+public class TwitterVerifyTask extends AsyncTask {
+ private Context context;
+ private View progressView;
+ private TwitterOauth twitterOauth;
+ private RewardVerifiedHandler handler;
+ private Exception error;
+
+ public TwitterVerifyTask(TwitterOauth twitterOauth, View progressView, Context context, RewardVerifiedHandler handler) {
+ this.twitterOauth = twitterOauth;
+ this.progressView = progressView;
+ this.context = context;
+ this.handler = handler;
+ }
+
+ protected void onPreExecute() {
+ Helper.setViewVisibility(progressView, View.VISIBLE);
+ }
+
+ protected RewardVerified doInBackground(Void... params) {
+ try {
+ Map options = new HashMap<>();
+ options.put("oauth_token", twitterOauth.getOauthToken());
+ options.put("oauth_token_secret", twitterOauth.getOauthTokenSecret());
+
+ JSONObject object = (JSONObject) Lbryio.parseResponse(Lbryio.call("verification", "twitter_verify", options, context));
+ Type type = new TypeToken(){}.getType();
+ Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+ return gson.fromJson(object.toString(), type);
+ } catch (Exception ex) {
+ error = ex;
+ return null;
+ }
+ }
+
+ protected void onPostExecute(RewardVerified result) {
+ Helper.setViewVisibility(progressView, View.GONE);
+ if (handler != null) {
+ if (result != null) {
+ handler.onSuccess(result);
+ } else {
+ handler.onError(error);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/io/lbry/browser/tasks/verification/TwitterAccessTokenTask.java b/app/src/main/java/io/lbry/browser/tasks/verification/TwitterAccessTokenTask.java
new file mode 100644
index 00000000..c114d8f0
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/tasks/verification/TwitterAccessTokenTask.java
@@ -0,0 +1,68 @@
+package io.lbry.browser.tasks.verification;
+
+import android.os.AsyncTask;
+
+import com.google.api.client.auth.oauth.OAuthHmacSigner;
+import com.google.api.client.auth.oauth.OAuthParameters;
+import com.google.api.client.http.GenericUrl;
+
+import io.lbry.browser.VerificationActivity;
+import io.lbry.browser.model.TwitterOauth;
+import io.lbry.browser.tasks.TwitterOauthHandler;
+import io.lbry.browser.utils.Helper;
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+public class TwitterAccessTokenTask extends AsyncTask {
+ private static final String ENDPOINT = "https://api.twitter.com/oauth/access_token";
+
+ private Exception error;
+ private String oauthParams;
+ private TwitterOauthHandler handler;
+
+ public TwitterAccessTokenTask(String oauthParams, TwitterOauthHandler handler) {
+ this.oauthParams = oauthParams;
+ this.handler = handler;
+ }
+
+ public String doInBackground(Void... params) {
+ try {
+ String url = String.format("%s?%s", ENDPOINT, oauthParams);
+ RequestBody body = RequestBody.create(new byte[0]);
+ Request request = new Request.Builder().url(url).post(body).build();
+
+ OkHttpClient client = new OkHttpClient.Builder().build();
+ Response response = client.newCall(request).execute();
+ return response.body().string();
+ } catch (Exception ex) {
+ error = ex;
+ return null;
+ }
+ }
+
+ protected void onPostExecute(String response) {
+ if (!Helper.isNullOrEmpty(response)) {
+ String[] pairs = response.split("&");
+ TwitterOauth twitterOauth = new TwitterOauth();
+ for (String pair : pairs) {
+ String[] parts = pair.split("=");
+ if (parts.length != 2) {
+ continue;
+ }
+ String key = parts[0];
+ String value = parts[1];
+ if ("oauth_token".equalsIgnoreCase(key)) {
+ twitterOauth.setOauthToken(value);
+ } else if ("oauth_token_secret".equalsIgnoreCase(key)) {
+ twitterOauth.setOauthTokenSecret(value);
+ }
+ }
+ handler.onSuccess(twitterOauth);
+ } else {
+ handler.onError(error);
+ }
+ }
+}
diff --git a/app/src/main/java/io/lbry/browser/tasks/verification/TwitterRequestTokenTask.java b/app/src/main/java/io/lbry/browser/tasks/verification/TwitterRequestTokenTask.java
new file mode 100644
index 00000000..4082f80b
--- /dev/null
+++ b/app/src/main/java/io/lbry/browser/tasks/verification/TwitterRequestTokenTask.java
@@ -0,0 +1,88 @@
+package io.lbry.browser.tasks.verification;
+
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.Base64;
+
+import com.google.api.client.auth.oauth.OAuthHmacSigner;
+import com.google.api.client.auth.oauth.OAuthParameters;
+import com.google.api.client.http.GenericUrl;
+
+import java.nio.charset.StandardCharsets;
+
+import io.lbry.browser.VerificationActivity;
+import io.lbry.browser.model.TwitterOauth;
+import io.lbry.browser.tasks.TwitterOauthHandler;
+import io.lbry.browser.utils.Helper;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+public class TwitterRequestTokenTask extends AsyncTask {
+ private static final String ENDPOINT = "https://api.twitter.com/oauth/request_token";
+
+ private String consumerKey;
+ private String consumerSecret;
+ private Exception error;
+ private TwitterOauthHandler handler;
+
+ public TwitterRequestTokenTask(String consumerKey, String consumerSecret, TwitterOauthHandler handler) {
+ this.consumerKey = consumerKey;
+ this.consumerSecret = consumerSecret;
+ this.handler = handler;
+ }
+
+ public String doInBackground(Void... params) {
+ try {
+
+ OAuthHmacSigner signer = new OAuthHmacSigner();
+ signer.clientSharedSecret = new String(
+ Base64.decode(consumerSecret, Base64.NO_WRAP), StandardCharsets.UTF_8.name());
+
+ OAuthParameters oauthParams = new OAuthParameters();
+ oauthParams.callback = "https://lbry.tv";
+ oauthParams.consumerKey = new String(
+ Base64.decode(consumerKey, Base64.NO_WRAP), StandardCharsets.UTF_8.name());;
+ oauthParams.signatureMethod = "HMAC-SHA-1";
+ oauthParams.signer = signer;
+ oauthParams.computeNonce();
+ oauthParams.computeTimestamp();
+ oauthParams.computeSignature("POST", new GenericUrl(ENDPOINT));
+
+ RequestBody body = RequestBody.create(new byte[0]);
+ Request request = new Request.Builder().url(ENDPOINT).addHeader(
+ "Authorization", oauthParams.getAuthorizationHeader()).post(body).build();
+
+ OkHttpClient client = new OkHttpClient.Builder().build();
+ Response response = client.newCall(request).execute();
+ return response.body().string();
+ } catch (Exception ex) {
+ error = ex;
+ return null;
+ }
+ }
+
+ protected void onPostExecute(String response) {
+ if (!Helper.isNullOrEmpty(response)) {
+ String[] pairs = response.split("&");
+ TwitterOauth twitterOauth = new TwitterOauth();
+ for (String pair : pairs) {
+ String[] parts = pair.split("=");
+ if (parts.length != 2) {
+ continue;
+ }
+ String key = parts[0];
+ String value = parts[1];
+ if ("oauth_token".equalsIgnoreCase(key)) {
+ twitterOauth.setOauthToken(value);
+ } else if ("oauth_token_secret".equalsIgnoreCase(key)) {
+ twitterOauth.setOauthTokenSecret(value);
+ }
+ }
+ handler.onSuccess(twitterOauth);
+ } else {
+ handler.onError(error);
+ }
+ }
+}
diff --git a/app/src/main/java/io/lbry/browser/ui/verification/ManualVerificationFragment.java b/app/src/main/java/io/lbry/browser/ui/verification/ManualVerificationFragment.java
index e901cb13..7bc09379 100644
--- a/app/src/main/java/io/lbry/browser/ui/verification/ManualVerificationFragment.java
+++ b/app/src/main/java/io/lbry/browser/ui/verification/ManualVerificationFragment.java
@@ -1,27 +1,75 @@
package io.lbry.browser.ui.verification;
+import android.content.Context;
+import android.graphics.Color;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.TextView;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
+import com.google.android.material.button.MaterialButton;
+import com.google.android.material.snackbar.Snackbar;
+
import io.lbry.browser.R;
import io.lbry.browser.listener.SignInListener;
+import io.lbry.browser.model.TwitterOauth;
+import io.lbry.browser.model.lbryinc.RewardVerified;
+import io.lbry.browser.tasks.RewardVerifiedHandler;
+import io.lbry.browser.tasks.TwitterOauthHandler;
+import io.lbry.browser.tasks.lbryinc.TwitterVerifyTask;
+import io.lbry.browser.tasks.verification.TwitterAccessTokenTask;
+import io.lbry.browser.tasks.verification.TwitterRequestTokenTask;
import io.lbry.browser.utils.Helper;
+import io.lbry.browser.utils.Lbryio;
import lombok.Setter;
public class ManualVerificationFragment extends Fragment {
@Setter
private SignInListener listener;
+ private PopupWindow popup;
+ private View mainView;
+ private View loadingView;
+
+ private TwitterOauth currentOauth;
+ private boolean twitterOauthInProgress = false;
+
+ private static final double SKIP_QUEUE_PRICE = 4.99;
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_verification_manual, container, false);
- Helper.applyHtmlForTextView((TextView) root.findViewById(R.id.verification_manual_discord_verify));
+ mainView = root.findViewById(R.id.verification_manual_main);
+ loadingView = root.findViewById(R.id.verification_manual_loading);
+
+ Context context = getContext();
+ MaterialButton buttonSkipQueue = root.findViewById(R.id.verification_manual_skip_queue);
+ buttonSkipQueue.setText(context.getString(R.string.skip_queue_button_text, String.valueOf(SKIP_QUEUE_PRICE)));
+
+ Helper.applyHtmlForTextView(root.findViewById(R.id.verification_manual_discord_verify));
+ root.findViewById(R.id.verification_manual_twitter_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // start twitter verification
+ if (currentOauth != null) {
+ // Twitter three-legged oauth already completed, verify directly
+ twitterVerify(currentOauth);
+ } else {
+ // show twitter sign-in flow
+ twitterVerificationFlow();
+ }
+ }
+ });
+
+
root.findViewById(R.id.verification_manual_continue_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -31,6 +79,205 @@ public class ManualVerificationFragment extends Fragment {
}
});
+ root.findViewById(R.id.verification_manual_skip_queue).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (listener != null) {
+ listener.onSkipQueueAction();
+ }
+ }
+ });
+
return root;
}
+
+ private void twitterVerificationFlow() {
+ twitterOauthInProgress = true;
+ if (listener != null) {
+ listener.onManualProgress(twitterOauthInProgress);
+ }
+ showLoading();
+ String consumerKey = getResources().getString(R.string.TWITTER_CONSUMER_KEY);
+ String consumerSecret = getResources().getString(R.string.TWITTER_CONSUMER_SECRET);
+ TwitterRequestTokenTask task = new TwitterRequestTokenTask(consumerKey, consumerSecret, new TwitterOauthHandler() {
+ @Override
+ public void onSuccess(TwitterOauth twitterOauth) {
+ twitterOauthInProgress = false;
+ if (listener != null) {
+ listener.onManualProgress(twitterOauthInProgress);
+ }
+ showTwitterAuthenticateWithToken(twitterOauth.getOauthToken());
+ }
+
+ @Override
+ public void onError(Exception error) {
+ handleFlowError(null);
+ }
+ });
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ public void showLoading() {
+ Helper.setViewVisibility(mainView, View.INVISIBLE);
+ Helper.setViewVisibility(loadingView, View.VISIBLE);
+ }
+
+ public void hideLoading() {
+ Helper.setViewVisibility(mainView, View.VISIBLE);
+ Helper.setViewVisibility(loadingView, View.GONE);
+ }
+
+ private void showTwitterAuthenticateWithToken(String oauthToken) {
+ Context context = getContext();
+ if (context != null) {
+ WebView webView = new WebView(context);
+ webView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
+ webView.loadUrl(String.format("https://api.twitter.com/oauth/authorize?oauth_token=%s", oauthToken));
+ webView.setWebViewClient(new WebViewClient() {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ if (url.startsWith("https://lbry.tv") || url.equalsIgnoreCase("https://twitter.com/home") /* Return to Twitter */) {
+ if (url.startsWith("https://lbry.tv") && url.contains("oauth_token") && url.contains("oauth_verifier")) {
+ // finish 3-legged oauth
+ twitterOauthInProgress = true;
+ listener.onManualProgress(twitterOauthInProgress);
+ finishTwitterOauth(url);
+ }
+
+ if (popup != null) {
+ popup.dismiss();
+ }
+ return false;
+ }
+
+ view.loadUrl(url);
+ return true;
+ }
+ });
+
+ View popupView = LayoutInflater.from(context).inflate(R.layout.popup_webview, null);
+ ((LinearLayout) popupView.findViewById(R.id.popup_webivew_container)).addView(webView);
+ popupView.findViewById(R.id.popup_cancel_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!twitterOauthInProgress && popup != null) {
+ popup.dismiss();
+ hideLoading();
+ }
+ }
+ });
+
+ float scale = getResources().getDisplayMetrics().density;
+ popup = new PopupWindow(context);
+ popup.setOnDismissListener(new PopupWindow.OnDismissListener() {
+ @Override
+ public void onDismiss() {
+ if (!twitterOauthInProgress) {
+ hideLoading();
+ }
+ popup = null;
+ }
+ });
+ popup.setWidth(Helper.getScaledValue(340, scale));
+ popup.setHeight(Helper.getScaledValue(480, scale));
+ popup.setContentView(popupView);
+
+ View parent = getView();
+ popup.setFocusable(true);
+ popup.showAtLocation(parent, Gravity.CENTER, 0, 0);
+ popup.update();
+ }
+ }
+
+ private void finishTwitterOauth(String callbackUrl) {
+ String params = callbackUrl.substring(callbackUrl.indexOf('?') + 1);
+ TwitterAccessTokenTask task = new TwitterAccessTokenTask(params, new TwitterOauthHandler() {
+ @Override
+ public void onSuccess(TwitterOauth twitterOauth) {
+ // send request to finish verifying
+ currentOauth = twitterOauth;
+ twitterVerify(twitterOauth);
+ }
+
+ @Override
+ public void onError(Exception error) {
+ handleFlowError(null);
+ }
+ });
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ private void twitterVerify(TwitterOauth twitterOauth) {
+ Context context = getContext();
+ if (context != null) {
+ showLoading();
+ twitterOauthInProgress = true;
+ if (listener != null) {
+ listener.onManualProgress(twitterOauthInProgress);
+ }
+
+ TwitterVerifyTask task = new TwitterVerifyTask(twitterOauth, null, context, new RewardVerifiedHandler() {
+ @Override
+ public void onSuccess(RewardVerified rewardVerified) {
+ twitterOauthInProgress = false;
+ if (listener != null) {
+ listener.onManualProgress(twitterOauthInProgress);
+ }
+
+ if (Lbryio.currentUser != null) {
+ Lbryio.currentUser.setRewardApproved(rewardVerified.isRewardApproved());
+ }
+ if (rewardVerified.isRewardApproved()) {
+ if (listener != null) {
+ listener.onTwitterVerified();
+ }
+ } else {
+ View root = getView();
+ if (root != null) {
+ // reward approved wasn't set to true
+ Snackbar.make(root, getString(R.string.twitter_verification_not_approved), Snackbar.LENGTH_LONG).
+ setTextColor(Color.WHITE).
+ setBackgroundTint(Color.RED).show();
+ }
+ hideLoading();
+ }
+ }
+
+ @Override
+ public void onError(Exception error) {
+ handleFlowError(error != null ? error.getMessage() : null);
+ }
+ });
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ twitterOauthInProgress = false;
+ if (listener != null) {
+ listener.onManualProgress(twitterOauthInProgress);
+ }
+ hideLoading();
+ }
+ }
+
+ private void handleFlowError(String extra) {
+ hideLoading();
+ twitterOauthInProgress = false;
+ if (listener != null) {
+ listener.onManualProgress(twitterOauthInProgress);
+ }
+ showFlowError(extra);
+ }
+
+ private void showFlowError(String extra) {
+ Context context = getContext();
+ View root = getView();
+ if (context != null && root != null) {
+ String message = !Helper.isNullOrEmpty(extra) ?
+ getString(R.string.twitter_account_ineligible, extra) :
+ getString(R.string.twitter_verification_failed);
+
+ Snackbar.make(root, message, Snackbar.LENGTH_LONG).
+ setTextColor(Color.WHITE).
+ setBackgroundTint(Color.RED).show();
+ }
+ }
}
diff --git a/app/src/main/java/io/lbry/browser/utils/Lbry.java b/app/src/main/java/io/lbry/browser/utils/Lbry.java
index 27c3ce38..1d0a7f5c 100644
--- a/app/src/main/java/io/lbry/browser/utils/Lbry.java
+++ b/app/src/main/java/io/lbry/browser/utils/Lbry.java
@@ -1,5 +1,7 @@
package io.lbry.browser.utils;
+import android.util.Log;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -128,7 +130,7 @@ public final class Lbry {
IS_STATUS_PARSED = true;
} catch (JSONException | LbryResponseException ex) {
// pass
- android.util.Log.e(TAG, "Could not parse status response.", ex);
+ Log.e(TAG, "Could not parse status response.", ex);
}
}
diff --git a/app/src/main/java/io/lbry/browser/utils/Lbryio.java b/app/src/main/java/io/lbry/browser/utils/Lbryio.java
index 0b3561ac..4f157605 100644
--- a/app/src/main/java/io/lbry/browser/utils/Lbryio.java
+++ b/app/src/main/java/io/lbry/browser/utils/Lbryio.java
@@ -101,6 +101,7 @@ public final class Lbryio {
}
url = uriBuilder.build().toString();
}
+
/*if (BuildConfig.DEBUG) {
Log.d(TAG, String.format("Request Method: %s, Sending request to URL: %s", method, url));
}*/
@@ -200,6 +201,7 @@ public final class Lbryio {
throw new LbryioResponseException("Unknown API error signature.", response.code());
}
} catch (JSONException | IOException ex) {
+
throw new LbryioResponseException(String.format("Could not parse response: %s", responseString), ex);
}
}
@@ -230,7 +232,7 @@ public final class Lbryio {
}
}
- android.util.Log.e(TAG, "Could not retrieve the current user", ex);
+ Log.e(TAG, "Could not retrieve the current user", ex);
return null;
}
}
@@ -308,7 +310,7 @@ public final class Lbryio {
context.sendBroadcast(intent);
}
} catch (Exception ex) {
- android.util.Log.e(TAG, "Error sending encrypted auth token action broadcast", ex);
+ Log.e(TAG, "Error sending encrypted auth token action broadcast", ex);
// pass
}
}
diff --git a/app/src/main/res/layout/activity_verification.xml b/app/src/main/res/layout/activity_verification.xml
index 04fddc26..95ef31fd 100644
--- a/app/src/main/res/layout/activity_verification.xml
+++ b/app/src/main/res/layout/activity_verification.xml
@@ -11,6 +11,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+
+
-
-
+
+
+ android:layout_height="match_parent">
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/popup_webview.xml b/app/src/main/res/layout/popup_webview.xml
new file mode 100644
index 00000000..f58d2423
--- /dev/null
+++ b/app/src/main/res/layout/popup_webview.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e5b527f8..92016e96 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -422,8 +422,18 @@
Please enter your phone number.
Not interested
Manual Reward Verification
+ Reward Verification
+ Social Media Verification
+ Skip the Queue
+ Skip for $%1$s
This account must undergo review before you can participate in the rewards program. This can take anywhere from several minutes to several days.
- If you continue to see this message, please request to be verified on the <a href="https://discordapp.com/invite/Z3bERWA">LBRY Discord server</a>.
+ You can get instantly verified to be able to participate in the rewards program using your Twitter account or skipping the manual verification queue.
+ Twitter Verification
+ Get instantly verified using your Twitter account. Your Twitter email address must match the email that you provided and your account should be active.
+ Verify with Twitter
+ Skip the Queue
+ Skip the manual verification queue by paying a fee in order to start participating in the rewards program immediately.
+ Please request to be verified on the <a href="https://discordapp.com/invite/Z3bERWA">LBRY Discord server</a>. A manual review can take anywhere from several minutes to several days.
Please enjoy free content in the meantime!
Verify Phone Number
Please enter the verification code sent to %1$s
@@ -432,6 +442,12 @@
Please enter a valid phone number.
Please enter the verification code sent to your phone number.
User account could not be retrieved at this time. Please try again later.
+ Your purchase request is still being processed. Please send an email to support@lbry.com.
+ Your purchase request could not be completed at this time. Please send an email to support@lbry.com.
+ Your Twitter account is not eligible at this time: %1$s
+ Your account was not approved for the rewards program. Please try again later.
+ Twitter verification failed. Please try again later.
+ You are now eligible to participate in the rewards program!
You have not added any tags yet. Add tags to improve discovery.
diff --git a/app/twitter.properties.secret b/app/twitter.properties.secret
new file mode 100644
index 00000000..9450afb8
Binary files /dev/null and b/app/twitter.properties.secret differ
diff --git a/lbry-android.keystore.secret b/lbry-android.keystore.secret
index 5ade2249..49c87412 100644
Binary files a/lbry-android.keystore.secret and b/lbry-android.keystore.secret differ