diff --git a/README.md b/README.md index c86b692f..0d152064 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ An Android browser and wallet for the [LBRY](https://lbry.com) network. ## Installation The minimum supported Android version is 5.0 Lollipop. There are two ways to install: -1. Via the Google Play Store. Anyone can join the [open beta](https://play.google.com/apps/testing/io.lbry.browser) in order to install the app from the Play Store. -1. Direct APK install available at [http://build.lbry.io/android/latest.apk](http://build.lbry.io/android/latest.apk). You will need to enable installation from third-party sources on your device in order to install from this source. +1. Via the F-Droid Android app. Search for LBRY and install the version you prefer. +1. Download the LBRY file from [the F-Droid website](https://f-droid.org). Search for LBRY on F-Droid's website, it should be listed as LBRY FDroid, download the APK file and install it on your device. ## Usage The app can be launched by opening **LBRY** from the device's app drawer or via the shortcut on the home screen if that was created upon installation. diff --git a/app/build.gradle b/app/build.gradle index 8cf07b06..26f53287 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -68,9 +68,9 @@ dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'com.google.android.material:material:1.3.0-alpha01' implementation "androidx.cardview:cardview:1.0.0" - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.navigation:navigation-fragment:2.2.2' - implementation 'androidx.navigation:navigation-ui:2.2.2' + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' + implementation 'androidx.navigation:navigation-fragment:2.3.0' + implementation 'androidx.navigation:navigation-ui:2.3.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' implementation 'androidx.preference:preference:1.1.1' @@ -80,6 +80,7 @@ dependencies { implementation 'androidx.camera:camera-lifecycle:1.0.0-beta03' implementation 'androidx.camera:camera-view:1.0.0-alpha10' implementation 'androidx.browser:browser:1.2.0' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'com.github.bumptech.glide:glide:4.11.0' implementation 'com.squareup.okhttp3:okhttp:4.4.1' @@ -111,10 +112,10 @@ dependencies { 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' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' - __32bitImplementation 'io.lbry:lbrysdk32:0.81.0' -// __64bitImplementation 'io.lbry:lbrysdk64:0.81.0' + __32bitImplementation 'io.lbry:lbrysdk32:0.82.0' +// __64bitImplementation 'io.lbry:lbrysdk64:0.82.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aee112fa..f7b63278 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,6 +57,10 @@ + + + + diff --git a/app/src/main/java/io/lbry/browser/FirstRunActivity.java b/app/src/main/java/io/lbry/browser/FirstRunActivity.java index b6d6eecb..46c85da7 100644 --- a/app/src/main/java/io/lbry/browser/FirstRunActivity.java +++ b/app/src/main/java/io/lbry/browser/FirstRunActivity.java @@ -201,7 +201,6 @@ public class FirstRunActivity extends AppCompatActivity { try { writer = new BufferedWriter(new FileWriter(file)); writer.write(installId); - android.util.Log.d("LbryMain", "Generated install ID=" + installId); } catch (IOException ex) { return false; } finally { diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index bc6ce0b9..e306f308 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -990,7 +990,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener fragment instanceof LibraryFragment || fragment instanceof SearchFragment; findViewById(R.id.floating_balance_main_container).setVisibility(!canShowFloatingBalance || inFullscreenMode ? View.INVISIBLE : View.VISIBLE); - if (!(fragment instanceof FileViewFragment) && !(fragment instanceof ShuffleFragment) && !inFullscreenMode) { + if (!(fragment instanceof FileViewFragment) && !(fragment instanceof ShuffleFragment) && !inFullscreenMode && nowPlayingClaim != null) { findViewById(R.id.global_now_playing_card).setVisibility(View.VISIBLE); } /*if (!Lbry.SDK_READY && !inFullscreenMode) { @@ -2772,9 +2772,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener try { LbryUri uri = LbryUri.parse(url); if (uri.isChannel()) { - openChannelUrl(url); + openChannelUrl(url.startsWith(LbryUri.PROTO_DEFAULT) ? url : uri.toString()); } else { - openFileUrl(url); + openFileUrl(url.startsWith(LbryUri.PROTO_DEFAULT) ? url : uri.toString()); } } catch (LbryUriException ex) { // pass @@ -3040,6 +3040,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener // TODO: Broadcast startup status changes JSONObject startupStatus = status.getJSONObject("startup_status"); + android.util.Log.d(TAG, startupStatus.toString(2)); sdkReady = startupStatus.getBoolean("file_manager") && startupStatus.getBoolean("wallet"); } } catch (ConnectException | JSONException ex) { diff --git a/app/src/main/java/io/lbry/browser/dialog/CreateSupportDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/CreateSupportDialogFragment.java index 51e63ec2..ac8b1181 100644 --- a/app/src/main/java/io/lbry/browser/dialog/CreateSupportDialogFragment.java +++ b/app/src/main/java/io/lbry/browser/dialog/CreateSupportDialogFragment.java @@ -225,9 +225,10 @@ public class CreateSupportDialogFragment extends BottomSheetDialogFragment imple if (!isTip) { sendButton.setText(R.string.send_revocable_support); } else { - String amountString = Helper.getValue(inputAmount.getText()); + String amountString = Helper.getValue(inputAmount.getText(), "0"); double parsedAmount = Helper.parseDouble(amountString, 0); - sendButton.setText(parsedAmount == 0 ? getString(R.string.send_a_tip) : getString(R.string.send_lbc_tip, amountString)); + String text = getResources().getQuantityString(R.plurals.send_lbc_tip, parsedAmount == 1.0 ? 1 : 2, amountString); + sendButton.setText(text); } } diff --git a/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java b/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java index 7828db03..2a68643f 100644 --- a/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/findcontent/FileViewFragment.java @@ -28,6 +28,7 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.AdapterView; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; @@ -1015,30 +1016,39 @@ public class FileViewFragment extends BaseFragment implements } if (claim != null) { - boolean isOwnClaim = Lbry.ownClaims.contains(claim); - if (isOwnClaim) { - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()). - setTitle(R.string.delete_content). - setMessage(R.string.confirm_delete_content_message) - .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - deleteCurrentClaim(); - } - }).setNegativeButton(R.string.no, null); - builder.show(); - } else { - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()). - setTitle(R.string.delete_file). - setMessage(R.string.confirm_delete_file_message) - .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - deleteClaimFile(); - } - }).setNegativeButton(R.string.no, null); - builder.show(); - } + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()). + setTitle(R.string.delete_file). + setMessage(R.string.confirm_delete_file_message) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + deleteClaimFile(); + } + }).setNegativeButton(R.string.no, null); + builder.show(); + } + } + }); + + root.findViewById(R.id.file_view_action_unpublish).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(root.findViewById(R.id.file_view_claim_display_area), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + if (claim != null) { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()). + setTitle(R.string.delete_content). + setMessage(R.string.confirm_delete_content_message) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + deleteCurrentClaim(); + } + }).setNegativeButton(R.string.no, null); + builder.show(); } } }); @@ -2778,10 +2788,13 @@ public class FileViewFragment extends BaseFragment implements boolean isOwnClaim = Lbry.ownClaims.contains(claim); View root = getView(); if (root != null) { - Helper.setViewVisibility(root.findViewById(R.id.file_view_action_download), isOwnClaim ? View.GONE : View.VISIBLE); Helper.setViewVisibility(root.findViewById(R.id.file_view_action_report), isOwnClaim ? View.GONE : View.VISIBLE); Helper.setViewVisibility(root.findViewById(R.id.file_view_action_edit), isOwnClaim ? View.VISIBLE : View.GONE); - Helper.setViewVisibility(root.findViewById(R.id.file_view_action_delete), isOwnClaim ? View.VISIBLE : View.GONE); + Helper.setViewVisibility(root.findViewById(R.id.file_view_action_unpublish), isOwnClaim ? View.VISIBLE : View.GONE); + + + LinearLayout fileViewActionsArea = root.findViewById(R.id.file_view_actions_area); + fileViewActionsArea.setWeightSum(isOwnClaim ? 6 : 5); } } } diff --git a/app/src/main/java/io/lbry/browser/utils/Helper.java b/app/src/main/java/io/lbry/browser/utils/Helper.java index e5ac6410..6d6a3862 100644 --- a/app/src/main/java/io/lbry/browser/utils/Helper.java +++ b/app/src/main/java/io/lbry/browser/utils/Helper.java @@ -348,6 +348,10 @@ public final class Helper { return cs != null ? cs.toString().trim() : ""; } + public static String getValue(CharSequence cs, String defaultValue) { + return cs != null && !Helper.isNullOrEmpty(cs.toString()) ? cs.toString().trim() : defaultValue; + } + public static List buildContentSortOrder(int sortBy) { List sortOrder = new ArrayList<>(); switch (sortBy) { diff --git a/app/src/main/java/io/lbry/browser/utils/LbryUri.java b/app/src/main/java/io/lbry/browser/utils/LbryUri.java index 94458cfa..7b543bb6 100644 --- a/app/src/main/java/io/lbry/browser/utils/LbryUri.java +++ b/app/src/main/java/io/lbry/browser/utils/LbryUri.java @@ -18,7 +18,7 @@ public class LbryUri { public static final int CLAIM_ID_MAX_LENGTH = 40; private static final String REGEX_PART_PROTOCOL = "^((?:lbry://|https://)?)"; - private static final String REGEX_PART_HOST = "((?:open.lbry.com/)?)"; + private static final String REGEX_PART_HOST = "((?:open.lbry.com/|lbry.tv/|lbry.lat/|lbry.fr/|lbry.in/)?)"; private static final String REGEX_PART_STREAM_OR_CHANNEL_NAME = "([^:$#/]*)"; private static final String REGEX_PART_MODIFIER_SEPARATOR = "([:$#]?)([^/]*)"; private static final String QUERY_STRING_BREAKER = "^([\\S]+)([?][\\S]*)"; @@ -128,9 +128,14 @@ public class LbryUri { boolean isChannel = includesChannel && Helper.isNullOrEmpty(possibleStreamName); String channelName = includesChannel && streamOrChannelName.length() > 1 ? streamOrChannelName.substring(1) : null; - // It would have thrown already on the RegEx parser if protocol value was incorrect - // open.lbry.com uses ':' as ModSeparators while lbry:// expects '#' - if (components.get(1).equals("open.lbry.com/")) { + /* + * It would have thrown already on the RegEx parser if protocol value was incorrect + * or HTTPS host was unknown to us. + * + * [https://] hosts use ':' as ModSeparators while [lbry://] protocol expects '#' + */ + + if (!components.get(1).isEmpty()) { if (primaryModSeparator.equals(":")) primaryModSeparator = "#"; if (secondaryModSeparator.equals(":")) diff --git a/app/src/main/res/drawable-anydpi/ic_unpublish.xml b/app/src/main/res/drawable-anydpi/ic_unpublish.xml new file mode 100644 index 00000000..07462043 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_unpublish.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/ic_credits.png b/app/src/main/res/drawable-hdpi/ic_credits.png new file mode 100644 index 00000000..dbf9c788 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_credits.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_unpublish.png b/app/src/main/res/drawable-hdpi/ic_unpublish.png new file mode 100644 index 00000000..b0c20cce Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_unpublish.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_credits.png b/app/src/main/res/drawable-mdpi/ic_credits.png new file mode 100644 index 00000000..dbf9c788 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_credits.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_unpublish.png b/app/src/main/res/drawable-mdpi/ic_unpublish.png new file mode 100644 index 00000000..b75d5bfb Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_unpublish.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_credits.png b/app/src/main/res/drawable-xhdpi/ic_credits.png new file mode 100644 index 00000000..dbf9c788 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_credits.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_unpublish.png b/app/src/main/res/drawable-xhdpi/ic_unpublish.png new file mode 100644 index 00000000..b867ffa0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_unpublish.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_credits.png b/app/src/main/res/drawable-xxhdpi/ic_credits.png new file mode 100644 index 00000000..dbf9c788 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_credits.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_unpublish.png b/app/src/main/res/drawable-xxhdpi/ic_unpublish.png new file mode 100644 index 00000000..bf9bcabd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_unpublish.png differ diff --git a/app/src/main/res/layout/card_wallet_send_credits.xml b/app/src/main/res/layout/card_wallet_send_credits.xml index 5a39b4d2..9cbd3f5d 100644 --- a/app/src/main/res/layout/card_wallet_send_credits.xml +++ b/app/src/main/res/layout/card_wallet_send_credits.xml @@ -72,10 +72,11 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:visibility="invisible"> - + - + - + diff --git a/app/src/main/res/layout/dialog_repost_claim.xml b/app/src/main/res/layout/dialog_repost_claim.xml index ada26944..8c9cbad2 100644 --- a/app/src/main/res/layout/dialog_repost_claim.xml +++ b/app/src/main/res/layout/dialog_repost_claim.xml @@ -127,10 +127,11 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:visibility="invisible"> - + - + android:src="@drawable/ic_credits" /> - + @@ -83,13 +84,11 @@ android:paddingStart="6dp" android:paddingEnd="7dp" android:visibility="gone"> - + android:src="@drawable/ic_credits" /> + + + + + + + + + android:textSize="12sp" /> + + android:layout_marginEnd="16dp" + android:layout_weight="10" + android:orientation="horizontal" + android:paddingTop="36dp"> + + android:textFontWeight="600" + android:textSize="12sp" /> + + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_weight="8" /> + android:background="@color/divider" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_publish_form.xml b/app/src/main/res/layout/fragment_publish_form.xml index 48078daf..cff1ad36 100644 --- a/app/src/main/res/layout/fragment_publish_form.xml +++ b/app/src/main/res/layout/fragment_publish_form.xml @@ -436,10 +436,11 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:visibility="invisible"> - + - + android:src="@drawable/ic_credits" /> - + android:src="@drawable/ic_credits" /> - + android:src="@drawable/ic_credits" /> Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International - LBC + LBRY Credits USD @@ -227,7 +227,7 @@ Oops! Something went wrong. Loaded Installation ID. Loaded local known and followed tags. - Loaded LBC/USD exchange rate. + Loaded LBRY Credits/USD exchange rate. User authenticated. Installation registered. Loaded subscriptions. @@ -298,6 +298,7 @@ Receive Spend Publish + Unpublish Support Abandon Channel @@ -313,7 +314,7 @@ <a href="https://lbry.com/faq/how-to-backup-wallet#android">Manual backup</a> <a href="https://lbry.com/faq/how-to-backup-wallet#sync">Sync FAQ</a> 0 - LBC + Credits Account Recommended A lbry.tv account allows you to earn rewards, backup your wallet, and keep everything in sync. @@ -337,7 +338,7 @@ Are you sure you want to unlock all your tips? Please enter an amount more than 0.0001 credits. - Buy LBC + Buy LBRY Credits Your device does not support the minimum requirements for securing your purchase request. You do not have a wallet address set. Please generate a new address and try again. @@ -381,7 +382,10 @@ Channel to show support as Make this a tip Send Revocable Support - Send a %1$s LBC Tip + + Tip %1$s Credit + Tip %1$s Credits + This will appear as a tip for %1$s, which will boost its ability to be discovered while active. <a href="https://lbry.com/faq/tipping">Learn more</a>. This will appear as a tip for %1$s, which will boost the channel\'s ability to be discovered while active. <a href="https://lbry.com/faq/tipping">Learn more</a>. Cancel @@ -464,7 +468,7 @@ Title \@ Deposit - This LBC remains yours. It is a deposit to reserve the name and can be undone at any time. + The credits remains yours. It is a deposit to reserve the name and can be undone at any time. LBRY requires access to download content to your device. LBRY requires access to load images from your device storage. Select thumbnail @@ -498,7 +502,7 @@ - LBRY credits allow you to publish or purchase content. + LBRY Credits allow you to publish or purchase content. You can obtain free credits worth $%1$s after you provide an email address. <a href="https://lbry.com/faq/earn-credits">Learn more</a>. Get started diff --git a/app/src/test/java/io/lbry/browser/utils/LbryUriTest.java b/app/src/test/java/io/lbry/browser/utils/LbryUriTest.java new file mode 100644 index 00000000..36ce5b49 --- /dev/null +++ b/app/src/test/java/io/lbry/browser/utils/LbryUriTest.java @@ -0,0 +1,130 @@ +package io.lbry.browser.utils; + +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.Test; + +import io.lbry.browser.exceptions.LbryUriException; + +import static org.junit.Assert.assertEquals; + +public class LbryUriTest { + private LbryUri expected; + + /* + * Create an LbryUri object and assign fields manually using class methods. This object will be + * compared with LbryUri.parse() returned object on each test. + */ + @Before + public void createExpected() { + expected = new LbryUri(); + expected.setChannelName("@lbry"); + expected.setStreamName("lbryturns4"); + + try { + LbryUri.UriModifier primaryMod = LbryUri.UriModifier.parse("#", "3f"); + LbryUri.UriModifier secondaryMod = LbryUri.UriModifier.parse("#", "6"); + expected.setChannelClaimId(primaryMod.getClaimId()); + expected.setStreamClaimId(secondaryMod.getClaimId()); + } catch (LbryUriException e) { + e.printStackTrace(); + } + } + + @Test + public void parseOpenLbryComWithChannel() { + LbryUri obtained = new LbryUri(); + + try { + obtained = LbryUri.parse("https://open.lbry.com/@lbry:3f/lbryturns4:6",false); + } catch (LbryUriException e) { + e.printStackTrace(); + } + + assertEquals(expected, obtained); + } + + @Test + public void parseLbryTvWithChannel() { + LbryUri obtained = new LbryUri(); + + try { + obtained = LbryUri.parse("https://lbry.tv/@lbry:3f/lbryturns4:6",false); + } catch (LbryUriException e) { + e.printStackTrace(); + } + + assertEquals(expected, obtained); + } + + @Test + public void parseLbryAlterWithChannel() { + LbryUri obtained = new LbryUri(); + + try { + obtained = LbryUri.parse("https://lbry.lat/@lbry:3f/lbryturns4:6",false); + } catch (LbryUriException e) { + e.printStackTrace(); + } + + assertEquals(expected, obtained); + } + + @Test + public void parseLbryProtocolWithChannel() { + LbryUri obtained = new LbryUri(); + + try { + obtained = LbryUri.parse("lbry://@lbry#3f/lbryturns4#6",false); + } catch (LbryUriException e) { + e.printStackTrace(); + } + + assertEquals(expected, obtained); + } + + @Test + public void parseLbryProtocolOnlyChannel() { + LbryUri expectedForChannel = sinthesizeExpected(); + + LbryUri obtained = new LbryUri(); + + try { + obtained = LbryUri.parse("lbry://@UCBerkeley#d",false); + } catch (LbryUriException e) { + e.printStackTrace(); + } + + assertEquals(expectedForChannel, obtained); + } + + @Test + public void parseLbryTvProtocolOnlyChannel() { + LbryUri expectedForChannel = sinthesizeExpected(); + + LbryUri obtained = new LbryUri(); + + try { + obtained = LbryUri.parse("https://lbry.tv/@UCBerkeley:d",false); + } catch (LbryUriException e) { + e.printStackTrace(); + } + + assertEquals(expectedForChannel, obtained); + } + + @NotNull + private LbryUri sinthesizeExpected() { + LbryUri expectedForChannel = new LbryUri(); + expectedForChannel.setChannelName("@UCBerkeley"); + expectedForChannel.setChannel(true); + + try { + LbryUri.UriModifier primaryMod = LbryUri.UriModifier.parse("#", "d"); + expectedForChannel.setChannelClaimId(primaryMod.getClaimId()); + } catch (LbryUriException e) { + e.printStackTrace(); + } + return expectedForChannel; + } +} \ No newline at end of file