diff --git a/app/build.gradle b/app/build.gradle index b6a9fdb..4140e14 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,6 +78,8 @@ dependencies { implementation 'com.hbb20:ccp:2.3.8' + implementation 'com.github.chrisbanes:PhotoView:2.3.0' + compileOnly 'org.projectlombok:lombok:1.18.10' annotationProcessor 'org.projectlombok:lombok:1.18.10' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' @@ -86,8 +88,8 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - __32bitImplementation files('libs/lbrysdk-0.72.0-release__arm.aar') - __64bitImplementation files('libs/lbrysdk-0.72.0-release__arm64.aar') + __32bitImplementation files('libs/lbrysdk-0.73.1-release__arm.aar') + __64bitImplementation files('libs/lbrysdk-0.73.1-release__arm64.aar') } apply plugin: 'com.google.gms.google-services' diff --git a/app/libs/lbrysdk-0.72.0-release__arm.aar b/app/libs/lbrysdk-0.73.1-release__arm.aar similarity index 70% rename from app/libs/lbrysdk-0.72.0-release__arm.aar rename to app/libs/lbrysdk-0.73.1-release__arm.aar index 4528d29..6058d44 100644 Binary files a/app/libs/lbrysdk-0.72.0-release__arm.aar and b/app/libs/lbrysdk-0.73.1-release__arm.aar differ diff --git a/app/libs/lbrysdk-0.72.0-release__arm64.aar b/app/libs/lbrysdk-0.73.1-release__arm64.aar similarity index 72% rename from app/libs/lbrysdk-0.72.0-release__arm64.aar rename to app/libs/lbrysdk-0.73.1-release__arm64.aar index c35f7be..525e693 100644 Binary files a/app/libs/lbrysdk-0.72.0-release__arm64.aar and b/app/libs/lbrysdk-0.73.1-release__arm64.aar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b5cb22f..ad1d564 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -80,5 +80,14 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/io/lbry/browser/FileViewActivity.java b/app/src/main/java/io/lbry/browser/FileViewActivity.java index 628f244..4904047 100644 --- a/app/src/main/java/io/lbry/browser/FileViewActivity.java +++ b/app/src/main/java/io/lbry/browser/FileViewActivity.java @@ -4,6 +4,7 @@ import android.annotation.SuppressLint; import android.app.PictureInPictureParams; import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; @@ -15,7 +16,6 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.service.voice.VoiceInteractionSession; import android.text.format.DateUtils; import android.view.View; import android.view.ViewGroup; @@ -25,21 +25,22 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.content.ContextCompat; import androidx.core.widget.NestedScrollView; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; +import com.github.chrisbanes.photoview.PhotoView; import com.google.android.exoplayer2.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; @@ -47,6 +48,10 @@ import com.google.android.flexbox.FlexboxLayoutManager; import com.google.android.material.button.MaterialButton; import com.google.android.material.snackbar.Snackbar; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.NumberFormat; @@ -58,42 +63,57 @@ import java.util.concurrent.TimeUnit; import io.lbry.browser.adapter.ClaimListAdapter; import io.lbry.browser.adapter.TagListAdapter; +import io.lbry.browser.dialog.RepostClaimDialogFragment; import io.lbry.browser.dialog.SendTipDialogFragment; import io.lbry.browser.exceptions.LbryUriException; +import io.lbry.browser.listener.WalletBalanceListener; import io.lbry.browser.model.Claim; import io.lbry.browser.model.ClaimCacheKey; import io.lbry.browser.model.Fee; -import io.lbry.browser.model.File; +import io.lbry.browser.model.LbryFile; import io.lbry.browser.model.Tag; import io.lbry.browser.model.lbryinc.Reward; -import io.lbry.browser.tasks.ClaimListResultHandler; -import io.lbry.browser.tasks.ClaimSearchTask; -import io.lbry.browser.tasks.FileListTask; +import io.lbry.browser.model.lbryinc.Subscription; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimSearchTask; +import io.lbry.browser.tasks.file.DeleteFileTask; +import io.lbry.browser.tasks.file.FileListTask; import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.tasks.file.GetFileTask; import io.lbry.browser.tasks.LighthouseSearchTask; -import io.lbry.browser.tasks.ResolveTask; +import io.lbry.browser.tasks.claim.ResolveTask; +import io.lbry.browser.tasks.lbryinc.ChannelSubscribeTask; import io.lbry.browser.tasks.lbryinc.ClaimRewardTask; import io.lbry.browser.tasks.lbryinc.FetchStatCountTask; import io.lbry.browser.tasks.lbryinc.LogFileViewTask; +import io.lbry.browser.ui.controls.SolidIconView; +import io.lbry.browser.ui.following.FollowingFragment; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; import io.lbry.browser.utils.LbryAnalytics; import io.lbry.browser.utils.LbryUri; +import io.lbry.browser.utils.Lbryio; +import io.lbry.lbrysdk.DownloadManager; +import io.lbry.lbrysdk.LbrynetService; public class FileViewActivity extends AppCompatActivity { public static FileViewActivity instance = null; private static final int RELATED_CONTENT_SIZE = 16; - private static final int SHARE_REQUEST_CODE = 3001; private static boolean startingShareActivity; + private boolean stopServiceReceived; + private boolean downloadInProgress; + private boolean downloadRequested; + private boolean walletBalanceInitialized; + private boolean inPictureInPictureMode; private boolean hasLoadedFirstBalance; private boolean loadFilePending; private boolean resolving; + private boolean initialFileLoadDone; private Claim claim; private String currentUrl; private ClaimListAdapter relatedContentAdapter; - private File file; private BroadcastReceiver sdkReceiver; private Player.EventListener fileViewPlayerListener; @@ -103,14 +123,10 @@ public class FileViewActivity extends AppCompatActivity { private ScheduledExecutorService elapsedPlaybackScheduler; private boolean playbackStarted; private long startTimeMillis; + private GetFileTask getFileTask; - private View buttonShareAction; - private View buttonTipAction; - private View buttonRepostAction; - private View buttonDownloadAction; - private View buttonEditAction; - private View buttonDeleteAction; - private View buttonReportAction; + private List walletBalanceListeners; + private BroadcastReceiver downloadEventReceiver; @Override protected void onCreate(Bundle savedInstanceState) { @@ -140,19 +156,22 @@ public class FileViewActivity extends AppCompatActivity { if (Lbry.claimCache.containsKey(key)) { claim = Lbry.claimCache.get(key); checkAndResetNowPlayingClaim(); - file = claim.getFile(); - if (file == null) { + if (claim.getFile() == null) { loadFile(); } } setContentView(R.layout.activity_file_view); + checkIsFileComplete(); currentUrl = url; logUrlEvent(url); if (claim == null) { + MainActivity.clearNowPlayingClaim(this); resolveUrl(url); } + walletBalanceListeners = new ArrayList<>(); + registerDownloadEventReceiver(); registerSdkReceiver(); fileViewPlayerListener = new Player.EventListener() { @@ -183,6 +202,16 @@ public class FileViewActivity extends AppCompatActivity { renderClaim(); } + public void addWalletBalanceListener(WalletBalanceListener listener) { + if (!walletBalanceListeners.contains(listener)) { + walletBalanceListeners.add(listener); + } + } + + public void removeWalletBalanceListener(WalletBalanceListener listener) { + walletBalanceListeners.remove(listener); + } + private void logUrlEvent(String url) { Bundle bundle = new Bundle(); bundle.putString("uri", url); @@ -212,9 +241,11 @@ public class FileViewActivity extends AppCompatActivity { view.setPlayer(null); view.setPlayer(MainActivity.appPlayer); } + return; } + initialFileLoadDone = false; currentUrl = newUrl; logUrlEvent(newUrl); resetViewCount(); @@ -228,13 +259,16 @@ public class FileViewActivity extends AppCompatActivity { if (Lbry.claimCache.containsKey(key)) { claim = Lbry.claimCache.get(key); checkAndResetNowPlayingClaim(); - file = claim.getFile(); - if (file == null) { + if (claim.getFile() == null) { loadFile(); + } else { + initialFileLoadDone = true; + checkInitialFileLoadDone(); } renderClaim(); } else { findViewById(R.id.file_view_claim_display_area).setVisibility(View.INVISIBLE); + MainActivity.clearNowPlayingClaim(this); resolveUrl(newUrl); } } @@ -243,18 +277,23 @@ public class FileViewActivity extends AppCompatActivity { private void registerSdkReceiver() { IntentFilter filter = new IntentFilter(); + filter.addAction(LbrynetService.ACTION_STOP_SERVICE); filter.addAction(MainActivity.ACTION_SDK_READY); filter.addAction(MainActivity.ACTION_WALLET_BALANCE_UPDATED); sdkReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equalsIgnoreCase(MainActivity.ACTION_SDK_READY)) { - // authenticate after we receive the sdk ready event + if (LbrynetService.ACTION_STOP_SERVICE.equalsIgnoreCase(action)) { + stopServiceReceived = true; + finish(); + } else if (MainActivity.ACTION_SDK_READY.equalsIgnoreCase(action)) { if (loadFilePending) { loadFile(); } - } else if (action.equalsIgnoreCase(MainActivity.ACTION_WALLET_BALANCE_UPDATED)) { + + initFloatingWalletBalance(); + } else if (MainActivity.ACTION_WALLET_BALANCE_UPDATED.equalsIgnoreCase(action)) { onWalletBalanceUpdated(); } } @@ -262,6 +301,20 @@ public class FileViewActivity extends AppCompatActivity { registerReceiver(sdkReceiver, filter); } + private void initFloatingWalletBalance() { + if (walletBalanceInitialized) { + return; + } + findViewById(R.id.floating_balance_container).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + sendBroadcast(new Intent(MainActivity.ACTION_OPEN_WALLET_PAGE)); + moveTaskToBack(true); + } + }); + walletBalanceInitialized = true; + } + private void onWalletBalanceUpdated() { if (Lbry.SDK_READY) { if (!hasLoadedFirstBalance) { @@ -272,12 +325,28 @@ public class FileViewActivity extends AppCompatActivity { ((TextView) findViewById(R.id.floating_balance_value)).setText( Helper.shortCurrencyFormat(Lbry.walletBalance.getAvailable().doubleValue())); + + for (WalletBalanceListener listener : walletBalanceListeners) { + if (listener != null) { + listener.onWalletBalanceUpdated(Lbry.walletBalance); + } + } } } private String getStreamingUrl() { - if (file != null && !Helper.isNullOrEmpty(file.getStreamingUrl())) { - return file.getStreamingUrl(); + LbryFile lbryFile = claim.getFile(); + if (lbryFile != null) { + if (!Helper.isNullOrEmpty(lbryFile.getDownloadPath()) && lbryFile.isCompleted()) { + File file = new File(lbryFile.getDownloadPath()); + if (file.exists()) { + return Uri.fromFile(file).toString(); + } + } + + if (!Helper.isNullOrEmpty(lbryFile.getStreamingUrl())) { + return lbryFile.getStreamingUrl(); + } } return buildLbryTvStreamingUrl(); @@ -295,28 +364,42 @@ public class FileViewActivity extends AppCompatActivity { } loadFilePending = false; - // TODO: Check if it's paid content and then wait for the user to explicitly request the file String claimId = claim.getClaimId(); FileListTask task = new FileListTask(claimId, null, new FileListTask.FileListResultHandler() { @Override - public void onSuccess(List files) { + public void onSuccess(List files) { if (files.size() > 0) { - file = files.get(0); - claim.setFile(file); + claim.setFile(files.get(0)); + checkIsFileComplete(); } + initialFileLoadDone = true; + checkInitialFileLoadDone(); } @Override public void onError(Exception error) { - + initialFileLoadDone = true; + checkInitialFileLoadDone(); } }); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + private void checkInitialFileLoadDone() { + if (initialFileLoadDone) { + restoreMainActionButton(); + } + if (claim != null && claim.isFree()) { + onMainActionButtonClicked(); + } + } + protected void onResume() { super.onResume(); MainActivity.startingFileViewActivity = false; + if (Lbry.SDK_READY) { + initFloatingWalletBalance(); + } } private void resolveUrl(String url) { @@ -326,7 +409,7 @@ public class FileViewActivity extends AppCompatActivity { ResolveTask task = new ResolveTask(url, Lbry.LBRY_TV_CONNECTION_STRING, loadingView, new ClaimListResultHandler() { @Override public void onSuccess(List claims) { - if (claims.size() > 0) { + if (claims.size() > 0 && !Helper.isNullOrEmpty(claims.get(0).getClaimId())) { claim = claims.get(0); if (Claim.TYPE_REPOST.equalsIgnoreCase(claim.getValueType())) { claim = claim.getRepostedClaim(); @@ -339,6 +422,8 @@ public class FileViewActivity extends AppCompatActivity { checkAndResetNowPlayingClaim(); loadFile(); renderClaim(); + } else { + // render nothing at location } } @@ -415,6 +500,93 @@ public class FileViewActivity extends AppCompatActivity { } }); + findViewById(R.id.file_view_action_repost).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(findViewById(R.id.file_view_claim_display_area), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + if (claim != null) { + RepostClaimDialogFragment dialog = RepostClaimDialogFragment.newInstance(); + dialog.setClaim(claim); + dialog.setListener(new RepostClaimDialogFragment.RepostClaimListener() { + @Override + public void onClaimReposted(Claim claim) { + Snackbar.make(findViewById(R.id.file_view_claim_display_area), R.string.content_successfully_reposted, Snackbar.LENGTH_LONG).show(); + } + }); + dialog.show(getSupportFragmentManager(), RepostClaimDialogFragment.TAG); + } + } + }); + + findViewById(R.id.file_view_action_delete).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(findViewById(R.id.file_view_claim_display_area), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(FileViewActivity.this). + 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(); + } + }); + + findViewById(R.id.file_view_action_download).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!Lbry.SDK_READY) { + Snackbar.make(findViewById(R.id.file_view_claim_display_area), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + return; + } + + if (claim != null) { + if (downloadInProgress) { + onDownloadAborted(); + + // file is already downloading and not completed + Intent intent = new Intent(LbrynetService.ACTION_DELETE_DOWNLOAD); + intent.putExtra("uri", claim.getPermanentUrl()); + intent.putExtra("nativeDelete", true); + sendBroadcast(intent); + } else { + downloadInProgress = true; + Helper.setViewVisibility(findViewById(R.id.file_view_download_progress), View.VISIBLE); + ((ImageView) findViewById(R.id.file_view_action_download_icon)).setImageResource(R.drawable.ic_stop); + + if (!claim.isFree()) { + downloadRequested = true; + onMainActionButtonClicked(); + } else { + // download the file + fileGet(true); + } + } + } + } + }); + + findViewById(R.id.file_view_action_report).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("https://lbry.com/dmca/%s", claim.getClaimId()))); + startActivity(intent); + } + } + }); + findViewById(R.id.player_toggle_full_screen).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -427,18 +599,96 @@ public class FileViewActivity extends AppCompatActivity { } }); + findViewById(R.id.file_view_publisher_name).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null && claim.getSigningChannel() != null) { + Claim publisher = claim.getSigningChannel(); + Intent intent = new Intent(MainActivity.ACTION_OPEN_CHANNEL_URL); + intent.putExtra("url", !Helper.isNullOrEmpty(publisher.getShortUrl()) ? publisher.getShortUrl() : publisher.getPermanentUrl()); + sendBroadcast(intent); + moveTaskToBack(true); + } + } + }); + + View buttonFollowUnfollow = findViewById(R.id.file_view_icon_follow_unfollow); + buttonFollowUnfollow.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (claim != null && claim.getSigningChannel() != null) { + Claim publisher = claim.getSigningChannel(); + boolean isFollowing = Lbryio.isFollowing(publisher); + Subscription subscription = Subscription.fromClaim(publisher); + buttonFollowUnfollow.setEnabled(false); + new ChannelSubscribeTask(FileViewActivity.this, publisher.getClaimId(), subscription, isFollowing, new ChannelSubscribeTask.ChannelSubscribeHandler() { + @Override + public void onSuccess() { + if (isFollowing) { + Lbryio.removeSubscription(subscription); + Lbryio.removeCachedResolvedSubscription(publisher); + } else { + Lbryio.addSubscription(subscription); + Lbryio.addCachedResolvedSubscription(publisher); + } + buttonFollowUnfollow.setEnabled(true); + checkIsFollowing(); + FollowingFragment.resetClaimSearchContent = true; + + // Save shared user state + sendBroadcast(new Intent(MainActivity.ACTION_SAVE_SHARED_USER_STATE)); + } + + @Override + public void onError(Exception exception) { + buttonFollowUnfollow.setEnabled(true); + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + }); + RecyclerView relatedContentList = findViewById(R.id.file_view_related_content_list); relatedContentList.setNestedScrollingEnabled(false); LinearLayoutManager llm = new LinearLayoutManager(this); relatedContentList.setLayoutManager(llm); } + private void deleteClaimFile() { + if (claim != null) { + View actionDelete = findViewById(R.id.file_view_action_delete); + DeleteFileTask task = new DeleteFileTask(claim.getClaimId(), new GenericTaskHandler() { + @Override + public void beforeStart() { + actionDelete.setEnabled(false); + } + + @Override + public void onSuccess() { + actionDelete.setVisibility(View.GONE); + findViewById(R.id.file_view_action_download).setVisibility(View.VISIBLE); + findViewById(R.id.file_view_unsupported_container).setVisibility(View.GONE); + actionDelete.setEnabled(true); + restoreMainActionButton(); + } + + @Override + public void onError(Exception error) { + actionDelete.setEnabled(true); + showError(error.getMessage()); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + private void renderClaim() { if (claim == null) { return; } loadViewCount(); + checkIsFollowing(); ((NestedScrollView) findViewById(R.id.file_view_scroll_view)).scrollTo(0, 0); findViewById(R.id.file_view_claim_display_area).setVisibility(View.VISIBLE); @@ -497,7 +747,6 @@ public class FileViewActivity extends AppCompatActivity { } }); - boolean isFree = true; if (metadata instanceof Claim.StreamMetadata) { Claim.StreamMetadata streamMetadata = (Claim.StreamMetadata) metadata; long publishTime = streamMetadata.getReleaseTime() > 0 ? streamMetadata.getReleaseTime() * 1000 : claim.getTimestamp() * 1000; @@ -506,23 +755,23 @@ public class FileViewActivity extends AppCompatActivity { Fee fee = streamMetadata.getFee(); if (fee != null && Helper.parseDouble(fee.getAmount(), 0) > 0) { - isFree = false; findViewById(R.id.file_view_fee_container).setVisibility(View.VISIBLE); ((TextView) findViewById(R.id.file_view_fee)).setText(Helper.shortCurrencyFormat(Helper.parseDouble(fee.getAmount(), 0))); } - MaterialButton mainActionButton = findViewById(R.id.file_view_main_action_button); - String mediaType = streamMetadata.getSource().getMediaType(); - if (mediaType.startsWith("audio") || mediaType.startsWith("video")) { - mainActionButton.setText(R.string.play); - } else if (mediaType.startsWith("text") || mediaType.startsWith("image")) { - mainActionButton.setText(R.string.view); - } else { - mainActionButton.setText(R.string.download); - } + } - if (isFree) { + MaterialButton mainActionButton = findViewById(R.id.file_view_main_action_button); + if (claim.isPlayable()) { + mainActionButton.setText(R.string.play); + } else if (claim.isViewable()) { + mainActionButton.setText(R.string.view); + } else { + mainActionButton.setText(R.string.download); + } + + if (claim.isFree() && (claim.isPlayable() || claim.isViewable())) { onMainActionButtonClicked(); } @@ -531,13 +780,18 @@ public class FileViewActivity extends AppCompatActivity { private void showUnsupportedView() { findViewById(R.id.file_view_exoplayer_container).setVisibility(View.GONE); - findViewById(R.id.file_view_unsupported_container).setVisibility(View.VISIBLE); + String fileNameString = ""; + if (claim.getFile() != null) { + LbryFile lbryFile = claim.getFile(); + File file = new File(lbryFile.getDownloadPath()); + fileNameString = String.format("\"%s\" ", file.getName()); + } + ((TextView) findViewById(R.id.file_view_unsupported_text)).setText(getString(R.string.unsupported_content_desc, fileNameString)); } private void showExoplayerView() { findViewById(R.id.file_view_unsupported_container).setVisibility(View.GONE); - findViewById(R.id.file_view_exoplayer_container).setVisibility(View.VISIBLE); } @@ -545,15 +799,14 @@ public class FileViewActivity extends AppCompatActivity { boolean newPlayerCreated = false; if (MainActivity.appPlayer == null) { MainActivity.appPlayer = new SimpleExoPlayer.Builder(this).build(); - MainActivity.appPlayer.setPlayWhenReady(true); - MainActivity.appPlayer.addListener(fileViewPlayerListener); newPlayerCreated = true; + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } + PlayerView view = findViewById(R.id.file_view_exoplayer_view); view.setPlayer(MainActivity.appPlayer); - if (MainActivity.nowPlayingClaim != null && MainActivity.nowPlayingClaim.getClaimId().equalsIgnoreCase(claim.getClaimId()) && !newPlayerCreated) { @@ -563,12 +816,17 @@ public class FileViewActivity extends AppCompatActivity { resetPlayer(); showBuffering(); + + MainActivity.appPlayer.addListener(fileViewPlayerListener); MainActivity.setNowPlayingClaim(claim, FileViewActivity.this); String userAgent = Util.getUserAgent(this, getString(R.string.app_name)); + + String mediaSourceUrl = getStreamingUrl(); MediaSource mediaSource = new ProgressiveMediaSource.Factory( new DefaultDataSourceFactory(this, userAgent), new DefaultExtractorsFactory() - ).createMediaSource(Uri.parse(getStreamingUrl())); + ).createMediaSource(Uri.parse(mediaSourceUrl)); + MainActivity.appPlayer.setPlayWhenReady(true); MainActivity.appPlayer.prepare(mediaSource, true, true); } @@ -608,12 +866,12 @@ public class FileViewActivity extends AppCompatActivity { Claim.GenericMetadata metadata = claim.getValue(); if (metadata instanceof Claim.StreamMetadata) { Claim.StreamMetadata streamMetadata = (Claim.StreamMetadata) metadata; - - Fee fee = streamMetadata.getFee(); - if (fee != null && Helper.parseDouble(fee.getAmount(), 0) > 0) { - // not free, perform a purchase - + if (claim.getFile() == null && !claim.isFree()) { + // not free (and the user does not own the claim yet), perform a purchase + confirmPurchaseUrl(); } else { + findViewById(R.id.file_view_main_action_button).setVisibility(View.INVISIBLE); + findViewById(R.id.file_view_main_action_loading).setVisibility(View.VISIBLE); handleMainActionForClaim(); } } else { @@ -621,32 +879,141 @@ public class FileViewActivity extends AppCompatActivity { } } - private void handleMainActionForClaim() { - startTimeMillis = System.currentTimeMillis(); - Claim.GenericMetadata metadata = claim.getValue(); - if (metadata instanceof Claim.StreamMetadata) { - Claim.StreamMetadata streamMetadata = (Claim.StreamMetadata) metadata; - // 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")) { + private void confirmPurchaseUrl() { + if (claim != null) { + Fee fee = ((Claim.StreamMetadata) claim.getValue()).getFee(); + double cost = Helper.parseDouble(fee.getAmount(), 0); + String message = getResources().getQuantityString(R.plurals.confirm_purchase_message, cost == 1 ? 1 : 2, claim.getTitle(), cost); + AlertDialog.Builder builder = new AlertDialog.Builder(this). + setTitle(R.string.confirm_purchase). + setMessage(message) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + Bundle bundle = new Bundle(); + bundle.putString("uri", currentUrl); + LbryAnalytics.logEvent(LbryAnalytics.EVENT_PURCHASE_URI, bundle); - } else if (mediaType.startsWith("image")) { - - } else { - // unsupported type - showUnsupportedView(); - } - } else { - showError(getString(R.string.cannot_view_claim)); + findViewById(R.id.file_view_main_action_button).setVisibility(View.INVISIBLE); + findViewById(R.id.file_view_main_action_loading).setVisibility(View.VISIBLE); + handleMainActionForClaim(); + } + }).setNegativeButton(R.string.no, null); + builder.show(); } } - private void showError(String message) { - Snackbar.make(findViewById(R.id.file_view_claim_display_area), message, Snackbar.LENGTH_LONG).setBackgroundTint(Color.RED).show(); + private void handleMainActionForClaim() { + if (Lbry.SDK_READY) { + // Check if the file already exists for the claim + if (claim.getFile() != null) { + playOrViewMedia(); + } else { + fileGet(downloadRequested || !claim.isPlayable()); + downloadRequested = false; + } + } else { + if (claim.isPlayable()) { + startTimeMillis = System.currentTimeMillis(); + showExoplayerView(); + playMedia(); + } else { + Snackbar.make(findViewById(R.id.file_view_global_layout), R.string.sdk_initializing_functionality, Snackbar.LENGTH_LONG).show(); + } + } + } + + private void fileGet(boolean save) { + if (getFileTask != null && getFileTask.getStatus() != AsyncTask.Status.FINISHED) { + return; + } + getFileTask = new GetFileTask(claim.getPermanentUrl(), save, null, new GetFileTask.GetFileHandler() { + @Override + public void beforeStart() { + + } + + @Override + public void onSuccess(LbryFile file, boolean saveFile) { + // queue the download + if (claim != null) { + if (!claim.isPlayable()) { + logFileView(claim.getPermanentUrl(), 0); + } + + claim.setFile(file); + if (saveFile) { + // download + String outpoint = String.format("%s:%d", claim.getTxid(), claim.getNout()); + Intent intent = new Intent(LbrynetService.ACTION_QUEUE_DOWNLOAD); + intent.putExtra("outpoint", outpoint); + sendBroadcast(intent); + } else { + // streaming + playOrViewMedia(); + } + } + } + + @Override + public void onError(Exception error) { + showError(getString(R.string.unable_to_view_url, currentUrl)); + restoreMainActionButton(); + } + }); + getFileTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void playOrViewMedia() { + boolean handled = false; + String mediaType = claim.getMediaType(); + if (!Helper.isNullOrEmpty(mediaType)) { + if (claim.isPlayable()) { + startTimeMillis = System.currentTimeMillis(); + showExoplayerView(); + playMedia(); + handled = true; + } else if (claim.isViewable()) { + // check type and display + if (mediaType.startsWith("image")) { + // display the image + View container = findViewById(R.id.file_view_imageviewer_container); + PhotoView photoView = findViewById(R.id.file_view_imageviewer); + + boolean fileExists = false; + LbryFile claimFile = claim.getFile(); + if (claimFile != null && !Helper.isNullOrEmpty(claimFile.getDownloadPath())) { + File file = new File(claimFile.getDownloadPath()); + fileExists = file.exists(); + + if (fileExists) { + Uri fileUri = Uri.fromFile(file); + Glide.with(getApplicationContext()).load(fileUri).centerInside().into(photoView); + hideFloatingWalletBalance(); + container.setVisibility(View.VISIBLE); + } + } + + if (!fileExists) { + showError(getString(R.string.claim_file_not_found, claimFile != null ? claimFile.getDownloadPath() : "")); + } + } else if (mediaType.startsWith("text")) { + // show browser (and parse markdown too) + } + handled = true; + } + } + + if (!handled) { + showUnsupportedView(); + } + } + + public void showError(String message) { + Snackbar.make(findViewById(R.id.file_view_claim_display_area), message, Snackbar.LENGTH_LONG). + setTextColor(Color.WHITE). + setBackgroundTint(Color.RED). + show(); } private void loadRelatedContent() { @@ -674,11 +1041,19 @@ public class FileViewActivity extends AppCompatActivity { 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); + if (claim.getName().startsWith("@")) { + // opening a channel + Intent intent = new Intent(MainActivity.ACTION_OPEN_CHANNEL_URL); + intent.putExtra("url", !Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl()); + sendBroadcast(intent); + moveTaskToBack(true); + } else { + Intent intent = new Intent(FileViewActivity.this, FileViewActivity.class); + intent.putExtra("claimId", claim.getClaimId()); + intent.putExtra("url", !Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl()); + MainActivity.startingFileViewActivity = true; + startActivity(intent); + } } }); @@ -703,13 +1078,30 @@ public class FileViewActivity extends AppCompatActivity { return; } + if (isImageViewerVisible()) { + findViewById(R.id.file_view_imageviewer_container).setVisibility(View.GONE); + return; + } + MainActivity.mainActive = true; Intent intent = new Intent(this, MainActivity.class); startActivity(intent); finish(); } + private boolean isImageViewerVisible() { + return findViewById(R.id.file_view_imageviewer_container).getVisibility() == View.VISIBLE; + } + + private boolean isWebViewVisible() { + return false; + } + protected void onUserLeaveHint() { + if (stopServiceReceived) { + return; + } + if (startingShareActivity) { // share activity triggered this, so reset the flag at this point new Handler().postDelayed(new Runnable() { @@ -727,20 +1119,25 @@ public class FileViewActivity extends AppCompatActivity { } protected void onStop() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (isInPictureInPictureMode() && MainActivity.appPlayer != null) { - MainActivity.appPlayer.setPlayWhenReady(false); - } + if (inPictureInPictureMode && MainActivity.appPlayer != null) { + MainActivity.appPlayer.setPlayWhenReady(false); } super.onStop(); } + protected void onDestroy() { + Helper.unregisterReceiver(downloadEventReceiver, this); Helper.unregisterReceiver(sdkReceiver, this); if (MainActivity.appPlayer != null && fileViewPlayerListener != null) { MainActivity.appPlayer.removeListener(fileViewPlayerListener); } instance = null; + + if (stopServiceReceived) { + MainActivity.stopExoplayer(); + } + super.onDestroy(); } @@ -757,6 +1154,7 @@ public class FileViewActivity extends AppCompatActivity { @Override public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { + inPictureInPictureMode = isInPictureInPictureMode; if (isInPictureInPictureMode) { renderPictureInPictureMode(); } else { @@ -829,6 +1227,9 @@ public class FileViewActivity extends AppCompatActivity { private void resetPlayer() { elapsedDuration = 0; totalDuration = 0; + renderElapsedDuration(); + renderTotalDuration(); + elapsedPlaybackScheduled = false; if (elapsedPlaybackScheduler != null) { elapsedPlaybackScheduler.shutdownNow(); @@ -837,6 +1238,8 @@ public class FileViewActivity extends AppCompatActivity { playbackStarted = false; startTimeMillis = 0; + + MainActivity.appPlayer.removeListener(fileViewPlayerListener); } private void showBuffering() { @@ -864,7 +1267,7 @@ public class FileViewActivity extends AppCompatActivity { bundle.putLong("time_to_start_seconds", Double.valueOf(timeToStartMillis / 1000.0).longValue()); LbryAnalytics.logEvent(LbryAnalytics.EVENT_PLAY, bundle); - logFileView(url, timeToStartMillis); + logFileView(claim.getPermanentUrl(), timeToStartMillis); } private void logFileView(String url, long timeToStart) { @@ -885,6 +1288,17 @@ public class FileViewActivity extends AppCompatActivity { } } + private void checkIsFollowing() { + if (claim != null && claim.getSigningChannel() != null) { + boolean isFollowing = Lbryio.isFollowing(claim.getSigningChannel()); + SolidIconView iconFollowUnfollow = findViewById(R.id.file_view_icon_follow_unfollow); + if (iconFollowUnfollow != null) { + iconFollowUnfollow.setText(isFollowing ? R.string.fa_heart_broken : R.string.fa_heart); + iconFollowUnfollow.setTextColor(ContextCompat.getColor(this, isFollowing ? R.color.foreground : R.color.red)); + } + } + } + private void claimEligibleRewards() { // attempt to claim eligible rewards after viewing or playing a file (fail silently) ClaimRewardTask firstStreamTask = new ClaimRewardTask(Reward.TYPE_FIRST_STREAM, null, null, this, eligibleRewardHandler); @@ -910,4 +1324,101 @@ public class FileViewActivity extends AppCompatActivity { // pass } }; + + private void registerDownloadEventReceiver() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_EVENT); + downloadEventReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String downloadAction = intent.getStringExtra("action"); + String uri = intent.getStringExtra("uri"); + String outpoint = intent.getStringExtra("outpoint"); + String fileInfoJson = intent.getStringExtra("file_info"); + + if (uri == null || outpoint == null || (fileInfoJson == null && !"abort".equals(downloadAction))) { + return; + } + + if (claim != null && !claim.getPermanentUrl().equalsIgnoreCase(uri)) { + return; + } + + if ("abort".equals(downloadAction)) { + // handle download aborted + onDownloadAborted(); + return; + } + + ImageView downloadIconView = findViewById(R.id.file_view_action_download_icon); + ProgressBar downloadProgressView = findViewById(R.id.file_view_download_progress); + + try { + JSONObject fileInfo = new JSONObject(fileInfoJson); + LbryFile claimFile = LbryFile.fromJSONObject(fileInfo); + claim.setFile(claimFile); + + if (DownloadManager.ACTION_START.equals(downloadAction)) { + downloadInProgress = true; + Helper.setViewVisibility(downloadProgressView, View.VISIBLE); + downloadProgressView.setProgress(0); + downloadIconView.setImageResource(R.drawable.ic_stop); + } else if (DownloadManager.ACTION_UPDATE.equals(downloadAction)) { + // handle download updated + downloadInProgress = true; + double progress = intent.getDoubleExtra("progress", 0); + Helper.setViewVisibility(downloadProgressView, View.VISIBLE); + downloadProgressView.setProgress(Double.valueOf(progress).intValue()); + downloadIconView.setImageResource(R.drawable.ic_stop); + } + else if (DownloadManager.ACTION_COMPLETE.equals(downloadAction)) { + downloadInProgress = false; + downloadProgressView.setProgress(100); + Helper.setViewVisibility(downloadProgressView, View.GONE); + playOrViewMedia(); + } + checkIsFileComplete(); + } catch (JSONException ex) { + // invalid file info for download + } + } + }; + registerReceiver(downloadEventReceiver, intentFilter); + } + + private void checkIsFileComplete() { + if (claim == null) { + return; + } + if (claim.getFile() != null && claim.getFile().isCompleted()) { + Helper.setViewVisibility(findViewById(R.id.file_view_action_delete), View.VISIBLE); + Helper.setViewVisibility(findViewById(R.id.file_view_action_download), View.GONE); + } else { + Helper.setViewVisibility(findViewById(R.id.file_view_action_delete), View.GONE); + Helper.setViewVisibility(findViewById(R.id.file_view_action_download), View.VISIBLE); + } + } + + private void hideFloatingWalletBalance() { + findViewById(R.id.floating_balance_main_container).setVisibility(View.GONE); + } + + private void onDownloadAborted() { + downloadInProgress = false; + + if (claim != null) { + claim.setFile(null); + } + ((ImageView) findViewById(R.id.file_view_action_download_icon)).setImageResource(R.drawable.ic_download); + Helper.setViewVisibility(findViewById(R.id.file_view_download_progress), View.GONE); + Helper.setViewVisibility(findViewById(R.id.file_view_unsupported_container), View.GONE); + + checkIsFileComplete(); + restoreMainActionButton(); + } + + private void restoreMainActionButton() { + findViewById(R.id.file_view_main_action_loading).setVisibility(View.INVISIBLE); + findViewById(R.id.file_view_main_action_button).setVisibility(View.VISIBLE); + } } diff --git a/app/src/main/java/io/lbry/browser/LocalFileProvider.java b/app/src/main/java/io/lbry/browser/LocalFileProvider.java new file mode 100644 index 0000000..87964c2 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/LocalFileProvider.java @@ -0,0 +1,7 @@ +package io.lbry.browser; + +import androidx.core.content.FileProvider; + +public class LocalFileProvider extends FileProvider { + +} diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index e19d205..654816c 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -25,8 +25,11 @@ import android.text.Editable; import android.text.TextWatcher; import android.util.Base64; import android.util.Log; +import android.view.KeyEvent; import android.view.View; import android.view.Menu; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.TextView; @@ -96,12 +99,12 @@ import io.lbry.browser.model.WalletBalance; import io.lbry.browser.model.WalletSync; import io.lbry.browser.model.lbryinc.Reward; import io.lbry.browser.model.lbryinc.Subscription; -import io.lbry.browser.tasks.ClaimListResultHandler; -import io.lbry.browser.tasks.ClaimListTask; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimListTask; import io.lbry.browser.tasks.lbryinc.FetchRewardsTask; import io.lbry.browser.tasks.LighthouseAutoCompleteTask; import io.lbry.browser.tasks.MergeSubscriptionsTask; -import io.lbry.browser.tasks.ResolveTask; +import io.lbry.browser.tasks.claim.ResolveTask; import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler; import io.lbry.browser.tasks.wallet.LoadSharedUserStateTask; import io.lbry.browser.tasks.wallet.SaveSharedUserStateTask; @@ -115,8 +118,9 @@ import io.lbry.browser.ui.channel.ChannelFragment; import io.lbry.browser.ui.channel.ChannelManagerFragment; import io.lbry.browser.ui.editorschoice.EditorsChoiceFragment; import io.lbry.browser.ui.following.FollowingFragment; +import io.lbry.browser.ui.other.AboutFragment; import io.lbry.browser.ui.search.SearchFragment; -import io.lbry.browser.ui.settings.SettingsFragment; +import io.lbry.browser.ui.other.SettingsFragment; import io.lbry.browser.ui.allcontent.AllContentFragment; import io.lbry.browser.ui.wallet.InvitesFragment; import io.lbry.browser.ui.wallet.RewardsFragment; @@ -133,6 +137,8 @@ import lombok.Getter; public class MainActivity extends AppCompatActivity implements SdkStatusListener { + private Map specialRouteFragmentClassMap; + private boolean inPictureInPictureMode; public static SimpleExoPlayer appPlayer; public static Claim nowPlayingClaim; public static boolean startingFilePickerActivity = false; @@ -141,24 +147,29 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener public static boolean startingSignInFlowActivity = false; public static boolean mainActive = false; private boolean enteringPIPMode = false; + + @Getter private String firebaseMessagingToken; private Map openNavFragments; private static final Map fragmentClassNavIdMap = new HashMap<>(); static { fragmentClassNavIdMap.put(FollowingFragment.class, NavMenuItem.ID_ITEM_FOLLOWING); - fragmentClassNavIdMap.put(WalletFragment.class, NavMenuItem.ID_ITEM_WALLET); - fragmentClassNavIdMap.put(SettingsFragment.class, NavMenuItem.ID_ITEM_SETTINGS); + fragmentClassNavIdMap.put(EditorsChoiceFragment.class, NavMenuItem.ID_ITEM_EDITORS_CHOICE); fragmentClassNavIdMap.put(AllContentFragment.class, NavMenuItem.ID_ITEM_ALL_CONTENT); fragmentClassNavIdMap.put(ChannelManagerFragment.class, NavMenuItem.ID_ITEM_CHANNELS); + fragmentClassNavIdMap.put(WalletFragment.class, NavMenuItem.ID_ITEM_WALLET); + fragmentClassNavIdMap.put(RewardsFragment.class, NavMenuItem.ID_ITEM_REWARDS); + fragmentClassNavIdMap.put(InvitesFragment.class, NavMenuItem.ID_ITEM_INVITES); + + fragmentClassNavIdMap.put(SettingsFragment.class, NavMenuItem.ID_ITEM_SETTINGS); + fragmentClassNavIdMap.put(AboutFragment.class, NavMenuItem.ID_ITEM_ABOUT); // Internal (sub-)pages fragmentClassNavIdMap.put(ChannelFragment.class, NavMenuItem.ID_ITEM_FOLLOWING); fragmentClassNavIdMap.put(SearchFragment.class, NavMenuItem.ID_ITEM_FOLLOWING); - - //fragmentClassNavIdMap.put(ChannelFormFragment.class, NavMenuItem.ID_ITEM_CHANNELS); } @@ -179,6 +190,10 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener public static final String ACTION_NOW_PLAYING_CLAIM_CLEARED = "io.lbry.browser.Broadcast.NowPlayingClaimCleared"; public static final String ACTION_OPEN_ALL_CONTENT_TAG = "io.lbry.browser.Broadcast.OpenAllContentTag"; public static final String ACTION_WALLET_BALANCE_UPDATED = "io.lbry.browser.Broadcast.WalletBalanceUpdated"; + public static final String ACTION_OPEN_CHANNEL_URL = "io.lbry.browser.Broadcast.OpenChannelUrl"; + public static final String ACTION_OPEN_WALLET_PAGE = "io.lbry.browser.Broadcast.OpenWalletPage"; + public static final String ACTION_OPEN_REWARDS_PAGE = "io.lbry.browser.Broadcast.OpenRewardsPage"; + public static final String ACTION_SAVE_SHARED_USER_STATE = "io.lbry.browser.Broadcast.SaveSharedUserState"; // preference keys public static final String PREFERENCE_KEY_DARK_MODE = "io.lbry.browser.preference.userinterface.DarkMode"; @@ -236,6 +251,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private boolean walletSyncScheduled; private String pendingAllContentTag; private String pendingChannelUrl; + private boolean pendingOpenWalletPage; + private boolean pendingOpenRewardsPage; private boolean pendingFollowingReload; // startup stages (to be able to determine how far a user made it if startup fails) @@ -262,7 +279,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener NavMenuItem.ID_ITEM_REWARDS, NavMenuItem.ID_ITEM_INVITES, - NavMenuItem.ID_ITEM_SETTINGS + NavMenuItem.ID_ITEM_SETTINGS, + NavMenuItem.ID_ITEM_ABOUT ); public boolean isDarkMode() { @@ -281,6 +299,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener if (!isDarkMode()) { getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } + initSpecialRouteMap(); LbryAnalytics.init(this); FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(new OnCompleteListener() { @@ -346,6 +365,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener @Override public void onClick(View view) { stopExoplayer(); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); nowPlayingClaim = null; findViewById(R.id.global_now_playing_card).setVisibility(View.GONE); } @@ -400,6 +420,24 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener }); } + private void initSpecialRouteMap() { + specialRouteFragmentClassMap = new HashMap<>(); + specialRouteFragmentClassMap.put("about", AboutFragment.class); + specialRouteFragmentClassMap.put("allContent", AllContentFragment.class); + specialRouteFragmentClassMap.put("channels", ChannelManagerFragment.class); + specialRouteFragmentClassMap.put("invite", InvitesFragment.class); + specialRouteFragmentClassMap.put("invites", InvitesFragment.class); + //specialRouteFragmentClassMap.put("library", LibraryFragment.class); + //specialRouteFragmentClassMap.put("publish", PublishFragment.class); + //specialRouteFragmentClassMap.put("publishes", PublishesFragment.class); + specialRouteFragmentClassMap.put("following", FollowingFragment.class); + specialRouteFragmentClassMap.put("rewards", RewardsFragment.class); + specialRouteFragmentClassMap.put("settings", SettingsFragment.class); + specialRouteFragmentClassMap.put("subscriptions", FollowingFragment.class); + specialRouteFragmentClassMap.put("wallet", WalletFragment.class); + specialRouteFragmentClassMap.put("discover", FollowingFragment.class); + } + protected void onNewIntent(Intent intent) { super.onNewIntent(intent); checkUrlIntent(intent); @@ -471,6 +509,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener case NavMenuItem.ID_ITEM_SETTINGS: openFragment(SettingsFragment.class, true, NavMenuItem.ID_ITEM_SETTINGS); break; + case NavMenuItem.ID_ITEM_ABOUT: + openFragment(AboutFragment.class, true, NavMenuItem.ID_ITEM_ABOUT); + break; } } @@ -586,7 +627,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener super.onDestroy(); } - private static void stopExoplayer() { + public static void stopExoplayer() { if (appPlayer != null) { appPlayer.stop(true); appPlayer.release(); @@ -612,6 +653,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener } Lbry.walletBalance = walletBalance; updateFloatingWalletBalance(); + updateUsdWalletBalanceInNav(); sendBroadcast(new Intent(ACTION_WALLET_BALANCE_UPDATED)); } @@ -636,19 +678,34 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener serviceRunning = isServiceRunning(this, LbrynetService.class); if (!serviceRunning) { Lbry.SDK_READY = false; + findViewById(R.id.global_sdk_initializing_status).setVisibility(View.VISIBLE); ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice"); } checkSdkReady(); showSignedInUser(); + checkPendingOpens(); - if (!Helper.isNullOrEmpty(pendingAllContentTag)) { - openAllContentFragmentWithTag(pendingAllContentTag); - pendingAllContentTag = null; + if (Lbry.SDK_READY) { + findViewById(R.id.global_sdk_initializing_status).setVisibility(View.GONE); } + } + + private void checkPendingOpens() { if (pendingFollowingReload) { loadFollowingContent(); pendingFollowingReload = false; } + if (!Helper.isNullOrEmpty(pendingAllContentTag)) { + openAllContentFragmentWithTag(pendingAllContentTag); + pendingAllContentTag = null; + } else if (!Helper.isNullOrEmpty(pendingChannelUrl)) { + openChannelUrl(pendingChannelUrl); + pendingChannelUrl = null; + } else if (pendingOpenWalletPage) { + openFragment(WalletFragment.class, true, NavMenuItem.ID_ITEM_WALLET); + } else if (pendingOpenRewardsPage) { + openFragment(RewardsFragment.class, true, NavMenuItem.ID_ITEM_REWARDS); + } } @Override @@ -679,7 +736,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener clearWunderbarFocus(view); } }); - findViewById(R.id.wunderbar).setOnFocusChangeListener(new View.OnFocusChangeListener() { + + EditText wunderbar = findViewById(R.id.wunderbar); + wunderbar.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View view, boolean hasFocus) { if (hasFocus) { @@ -693,7 +752,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener } }); - ((EditText) findViewById(R.id.wunderbar)).addTextChangedListener(new TextWatcher() { + wunderbar.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { @@ -711,6 +770,40 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener } }); + wunderbar.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { + if (actionId == EditorInfo.IME_ACTION_GO) { + String input = Helper.getValue(wunderbar.getText()); + boolean handled = false; + if (input.startsWith(LbryUri.PROTO_DEFAULT) && !input.equalsIgnoreCase(LbryUri.PROTO_DEFAULT)) { + try { + LbryUri uri = LbryUri.parse(input); + if (uri.isChannel()) { + openChannelUrl(uri.toString()); + clearWunderbarFocus(wunderbar); + handled = true; + } else { + openFileUrl(uri.toString(), MainActivity.this); + clearWunderbarFocus(wunderbar); + handled = true; + } + } catch (LbryUriException ex) { + // pass + } + } + if (!handled) { + // search + launchSearch(input); + clearWunderbarFocus(wunderbar); + } + + return true; + } + + return false; + } + }); urlSuggestionListAdapter = new UrlSuggestionListAdapter(this); urlSuggestionListAdapter.setListener(new UrlSuggestionListAdapter.UrlSuggestionClickListener() { @@ -942,6 +1035,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener playerView.setPlayer(null); playerView.setPlayer(appPlayer); playerView.setUseController(false); + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } @@ -1014,6 +1109,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener checkSyncedWallet(); } + findViewById(R.id.global_sdk_initializing_status).setVisibility(View.GONE); scheduleWalletBalanceUpdate(); scheduleWalletSyncTask(); fetchChannels(); @@ -1045,6 +1141,16 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener }); } + private void updateUsdWalletBalanceInNav() { + double usdBalance = Lbry.walletBalance.getAvailable().doubleValue() * Lbryio.LBCUSDRate; + if (navMenuAdapter != null) { + navMenuAdapter.setExtraLabelForItem( + NavMenuItem.ID_ITEM_WALLET, + Lbryio.LBCUSDRate > 0 ? String.format("$%s", Helper.USD_CURRENCY_FORMAT.format(usdBalance)) : null + ); + } + } + private void updateFloatingWalletBalance() { if (!hasLoadedFirstBalance) { findViewById(R.id.floating_balance_loading).setVisibility(View.GONE); @@ -1222,18 +1328,30 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private void registerRequestsReceiver() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_AUTH_TOKEN_GENERATED); - intentFilter.addAction(ACTION_OPEN_ALL_CONTENT_TAG); intentFilter.addAction(ACTION_USER_SIGN_IN_SUCCESS); + intentFilter.addAction(ACTION_OPEN_ALL_CONTENT_TAG); + intentFilter.addAction(ACTION_OPEN_CHANNEL_URL); + intentFilter.addAction(ACTION_OPEN_WALLET_PAGE); + intentFilter.addAction(ACTION_OPEN_REWARDS_PAGE); + intentFilter.addAction(ACTION_SAVE_SHARED_USER_STATE); requestsReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ACTION_AUTH_TOKEN_GENERATED.equalsIgnoreCase(action)) { handleAuthTokenGenerated(intent); - } else if (ACTION_OPEN_ALL_CONTENT_TAG.equalsIgnoreCase(action)) { - handleOpenContentTag(intent); } else if (ACTION_USER_SIGN_IN_SUCCESS.equalsIgnoreCase(action)) { handleUserSignInSuccess(intent); + } else if (ACTION_OPEN_ALL_CONTENT_TAG.equalsIgnoreCase(action)) { + handleOpenContentTag(intent); + } else if (ACTION_OPEN_CHANNEL_URL.equalsIgnoreCase(action)) { + handleOpenChannelUrl(intent); + } else if (ACTION_OPEN_WALLET_PAGE.equalsIgnoreCase(action)) { + pendingOpenWalletPage = true; + } else if (ACTION_OPEN_REWARDS_PAGE.equalsIgnoreCase(action)) { + pendingOpenRewardsPage = true; + } else if (ACTION_SAVE_SHARED_USER_STATE.equalsIgnoreCase(action)) { + saveSharedUserState(); } } @@ -1253,7 +1371,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private void handleUserSignInSuccess(Intent intent) { pendingFollowingReload = true; } - private void handleOpenChannelUrl(String url) { + private void handleOpenChannelUrl(Intent intent) { + String url = intent.getStringExtra("url"); pendingChannelUrl = url; } }; @@ -1294,6 +1413,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener findViewById(R.id.global_now_playing_card).setVisibility(View.GONE); ((TextView) findViewById(R.id.global_now_playing_title)).setText(null); ((TextView) findViewById(R.id.global_now_playing_channel_title)).setText(null); + if (MainActivity.appPlayer != null) { + MainActivity.appPlayer.setPlayWhenReady(false); + } } }; registerReceiver(userActionsReceiver, intentFilter); @@ -1592,6 +1714,13 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener if (Lbryio.totalUnclaimedRewardAmount > 0) { showFloatingUnclaimedRewards(); + double usdRewardAmount = Lbryio.totalUnclaimedRewardAmount * Lbryio.LBCUSDRate; + if (navMenuAdapter != null) { + navMenuAdapter.setExtraLabelForItem( + NavMenuItem.ID_ITEM_REWARDS, + Lbryio.LBCUSDRate > 0 ? String.format("$%s", Helper.USD_CURRENCY_FORMAT.format(usdRewardAmount)) : null + ); + } } } @@ -1620,9 +1749,15 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener String url = data.toString(); // check special urls if (url.startsWith("lbry://?")) { - String pagePath = url.substring(8); + String specialPath = url.substring(8); + if (specialRouteFragmentClassMap.containsKey(specialPath)) { + Class fragmentClass = specialRouteFragmentClassMap.get(specialPath); + if (fragmentClassNavIdMap.containsKey(fragmentClass)) { + openFragment(specialRouteFragmentClassMap.get(specialPath), true, fragmentClassNavIdMap.get(fragmentClass)); + } + } - // TODO: Handle special page paths + // unrecognised path will open the following by default } else { try { LbryUri uri = LbryUri.parse(url); @@ -1770,10 +1905,10 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener )); yourContentGroup.setItems(Arrays.asList( - new NavMenuItem(NavMenuItem.ID_ITEM_NEW_PUBLISH, R.string.fa_upload, R.string.new_publish, "NewPublish", context), - new NavMenuItem(NavMenuItem.ID_ITEM_CHANNELS, R.string.fa_at, R.string.channels, "Channels", context), - new NavMenuItem(NavMenuItem.ID_ITEM_LIBRARY, R.string.fa_download, R.string.library, "Library", context), - new NavMenuItem(NavMenuItem.ID_ITEM_PUBLISHES, R.string.fa_cloud_upload, R.string.publishes, "Publishes", context) + //new NavMenuItem(NavMenuItem.ID_ITEM_NEW_PUBLISH, R.string.fa_upload, R.string.new_publish, "NewPublish", context), + new NavMenuItem(NavMenuItem.ID_ITEM_CHANNELS, R.string.fa_at, R.string.channels, "Channels", context) + //new NavMenuItem(NavMenuItem.ID_ITEM_LIBRARY, R.string.fa_download, R.string.library, "Library", context) + //new NavMenuItem(NavMenuItem.ID_ITEM_PUBLISHES, R.string.fa_cloud_upload, R.string.publishes, "Publishes", context) )); walletGroup.setItems(Arrays.asList( @@ -1837,7 +1972,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener // TODO: Broadcast startup status changes JSONObject startupStatus = status.getJSONObject("startup_status"); - sdkReady = startupStatus.getBoolean("stream_manager") && startupStatus.getBoolean("wallet"); + sdkReady = startupStatus.getBoolean("file_manager") && startupStatus.getBoolean("wallet"); } } catch (ConnectException ex) { // pass @@ -1921,6 +2056,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener @Override public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { + inPictureInPictureMode = isInPictureInPictureMode; enteringPIPMode = false; if (isInPictureInPictureMode) { // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode. @@ -1932,10 +2068,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener } protected void onStop() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (!MainActivity.startingFileViewActivity && appPlayer != null && isInPictureInPictureMode()) { - appPlayer.setPlayWhenReady(false); - } + if (!MainActivity.startingFileViewActivity && appPlayer != null && inPictureInPictureMode) { + appPlayer.setPlayWhenReady(false); } super.onStop(); } @@ -2044,5 +2178,4 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener public static boolean hasPermission(String permission, Context context) { return (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED); } - } diff --git a/app/src/main/java/io/lbry/browser/adapter/NavigationMenuAdapter.java b/app/src/main/java/io/lbry/browser/adapter/NavigationMenuAdapter.java index 2f1de9b..f5c75a5 100644 --- a/app/src/main/java/io/lbry/browser/adapter/NavigationMenuAdapter.java +++ b/app/src/main/java/io/lbry/browser/adapter/NavigationMenuAdapter.java @@ -16,6 +16,7 @@ import java.util.List; import io.lbry.browser.R; import io.lbry.browser.model.NavMenuItem; import io.lbry.browser.ui.controls.SolidIconView; +import io.lbry.browser.utils.Helper; import lombok.Getter; import lombok.Setter; @@ -44,6 +45,16 @@ public class NavigationMenuAdapter extends RecyclerView.Adapter adapterView, View view, int position, long l) { + Object item = adapterView.getItemAtPosition(position); + if (item instanceof Claim) { + Claim claim = (Claim) item; + textNamePrefix.setText(String.format("%s%s/", LbryUri.PROTO_DEFAULT, claim.getName())); + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + inputDeposit.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + inputDeposit.setHint(hasFocus ? getString(R.string.zero) : ""); + inlineBalanceContainer.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE); + } + }); + + linkCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + linkToggleAdvanced.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (advancedContainer.getVisibility() != View.VISIBLE) { + advancedContainer.setVisibility(View.VISIBLE); + linkToggleAdvanced.setText(R.string.hide_advanced); + } else { + advancedContainer.setVisibility(View.GONE); + linkToggleAdvanced.setText(R.string.show_advanced); + } + } + }); + + buttonRepost.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + validateAndRepostClaim(); + } + }); + + onWalletBalanceUpdated(Lbry.walletBalance); + + return view; + } + + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context instanceof FileViewActivity) { + ((FileViewActivity) context).addWalletBalanceListener(this); + } + fetchChannels(); + } + + public void onPause() { + Context context = getContext(); + if (context instanceof FileViewActivity) { + ((FileViewActivity) context).removeWalletBalanceListener(this); + } + inputDeposit.clearFocus(); + super.onPause(); + } + + + private void fetchChannels() { + if (Lbry.ownChannels == null || Lbry.ownChannels.size() == 0) { + startLoading(); + ClaimListTask task = new ClaimListTask(Claim.TYPE_CHANNEL, repostProgress, new ClaimListResultHandler() { + @Override + public void onSuccess(List claims) { + Lbry.ownChannels = new ArrayList<>(claims); + loadChannels(claims); + finishLoading(); + } + + @Override + public void onError(Exception error) { + // could not fetch channels + Context context = getContext(); + if (context instanceof FileViewActivity) { + ((FileViewActivity) context).showError(error.getMessage()); + } + dismiss(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + loadChannels(Lbry.ownChannels); + } + } + + private void loadChannels(List channels) { + if (channelSpinnerAdapter == null) { + Context context = getContext(); + channelSpinnerAdapter = new InlineChannelSpinnerAdapter(context, R.layout.spinner_item_channel, channels); + channelSpinner.setAdapter(channelSpinnerAdapter); + channelSpinnerAdapter.notifyDataSetChanged(); + } else { + channelSpinnerAdapter.clear(); + channelSpinnerAdapter.addAll(channels); + channelSpinnerAdapter.notifyDataSetChanged(); + } + } + + @Override + public void onWalletBalanceUpdated(WalletBalance walletBalance) { + if (walletBalance != null && inlineBalanceValue != null) { + inlineBalanceValue.setText(Helper.shortCurrencyFormat(walletBalance.getAvailable().doubleValue())); + } + } + + private void validateAndRepostClaim() { + String name = Helper.getValue(inputName.getText()); + if (Helper.isNullOrEmpty(name) || !LbryUri.isNameValid(name)) { + showError(getString(R.string.repost_name_invalid_characters)); + return; + } + + String depositString = Helper.getValue(inputDeposit.getText()); + if (Helper.isNullOrEmpty(depositString)) { + showError(getString(R.string.invalid_amount)); + return; + } + + BigDecimal bid = new BigDecimal(depositString); + if (bid.doubleValue() > Lbry.walletBalance.getAvailable().doubleValue()) { + showError(getString(R.string.insufficient_balance)); + return; + } + + Claim channel = (Claim) channelSpinner.getSelectedItem(); + StreamRepostTask task = new StreamRepostTask(name, bid, claim.getClaimId(), channel.getClaimId(), repostProgress, new ClaimResultHandler() { + @Override + public void beforeStart() { + startLoading(); + } + + @Override + public void onSuccess(Claim claimResult) { + if (listener != null) { + listener.onClaimReposted(claimResult); + } + finishLoading(); + dismiss(); + } + + @Override + public void onError(Exception error) { + showError(error.getMessage()); + finishLoading(); + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void showError(String message) { + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED). + setTextColor(Color.WHITE). + show(); + } + + private void startLoading() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.setCanceledOnTouchOutside(false); + } + linkCancel.setEnabled(false); + buttonRepost.setEnabled(false); + inputName.setEnabled(false); + channelSpinner.setEnabled(false); + linkToggleAdvanced.setVisibility(View.INVISIBLE); + } + private void finishLoading() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.setCanceledOnTouchOutside(true); + } + linkCancel.setEnabled(true); + buttonRepost.setEnabled(true); + inputName.setEnabled(true); + channelSpinner.setEnabled(true); + linkToggleAdvanced.setVisibility(View.VISIBLE); + } + + public interface RepostClaimListener { + void onClaimReposted(Claim claim); + } +} diff --git a/app/src/main/java/io/lbry/browser/dialog/SendTipDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/SendTipDialogFragment.java index 1f97d49..6d9151a 100644 --- a/app/src/main/java/io/lbry/browser/dialog/SendTipDialogFragment.java +++ b/app/src/main/java/io/lbry/browser/dialog/SendTipDialogFragment.java @@ -1,6 +1,8 @@ package io.lbry.browser.dialog; +import android.app.Dialog; import android.content.Context; +import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; import android.text.method.LinkMovementMethod; @@ -21,6 +23,7 @@ import com.google.android.material.textfield.TextInputEditText; import java.math.BigDecimal; import java.text.DecimalFormat; +import io.lbry.browser.FileViewActivity; import io.lbry.browser.MainActivity; import io.lbry.browser.R; import io.lbry.browser.listener.WalletBalanceListener; @@ -52,12 +55,18 @@ public class SendTipDialogFragment extends BottomSheetDialogFragment implements } private void disableControls() { - getDialog().setCanceledOnTouchOutside(false); + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.setCanceledOnTouchOutside(false); + } sendButton.setEnabled(false); cancelLink.setEnabled(false); } private void enableControls() { - getDialog().setCanceledOnTouchOutside(true); + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.setCanceledOnTouchOutside(true); + } sendButton.setEnabled(true); cancelLink.setEnabled(true); } @@ -155,15 +164,15 @@ public class SendTipDialogFragment extends BottomSheetDialogFragment implements public void onResume() { super.onResume(); Context context = getContext(); - if (context instanceof MainActivity) { - ((MainActivity) context).addWalletBalanceListener(this); + if (context instanceof FileViewActivity) { + ((FileViewActivity) context).addWalletBalanceListener(this); } } public void onPause() { Context context = getContext(); - if (context instanceof MainActivity) { - ((MainActivity) context).removeWalletBalanceListener(this); + if (context instanceof FileViewActivity) { + ((FileViewActivity) context).removeWalletBalanceListener(this); } super.onPause(); } @@ -176,8 +185,10 @@ public class SendTipDialogFragment extends BottomSheetDialogFragment implements } private void showError(String message) { - Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).setBackgroundTint( - ContextCompat.getColor(getContext(), R.color.red)).show(); + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED). + setTextColor(Color.WHITE). + show(); } public interface SendTipListener { diff --git a/app/src/main/java/io/lbry/browser/model/Claim.java b/app/src/main/java/io/lbry/browser/model/Claim.java index 123aa04..3f24dde 100644 --- a/app/src/main/java/io/lbry/browser/model/Claim.java +++ b/app/src/main/java/io/lbry/browser/model/Claim.java @@ -79,7 +79,57 @@ public class Claim { private String repostChannelUrl; private boolean isChannelSignatureValid; private GenericMetadata value; - private File file; // associated file if it exists + private LbryFile file; // associated file if it exists + + public static Claim claimFromOutput(JSONObject item) { + // we only need name, permanent_url, txid and nout + Claim claim = new Claim(); + claim.setClaimId(Helper.getJSONString("claim_id", null, item)); + claim.setName(Helper.getJSONString("name", null, item)); + claim.setPermanentUrl(Helper.getJSONString("permanent_url", null, item)); + claim.setTxid(Helper.getJSONString("txid", null, item)); + claim.setNout(Helper.getJSONInt("nout", -1, item)); + return claim; + } + + public boolean isFree() { + if (!(value instanceof StreamMetadata)) { + return true; + } + + Fee fee = ((StreamMetadata) value).getFee(); + return fee == null || Helper.parseDouble(fee.getAmount(), 0) == 0; + } + + public String getMediaType() { + if (value instanceof StreamMetadata) { + StreamMetadata metadata = (StreamMetadata) value; + String mediaType = metadata.getSource() != null ? metadata.getSource().getMediaType() : null; + return mediaType; + } + return null; + } + + public boolean isPlayable() { + if (value instanceof StreamMetadata) { + StreamMetadata metadata = (StreamMetadata) value; + String mediaType = metadata.getSource() != null ? metadata.getSource().getMediaType() : null; + if (mediaType != null) { + return mediaType.startsWith("video") || mediaType.startsWith("audio"); + } + } + return false; + } + public boolean isViewable() { + if (value instanceof StreamMetadata) { + StreamMetadata metadata = (StreamMetadata) value; + String mediaType = metadata.getSource() != null ? metadata.getSource().getMediaType() : null; + if (mediaType != null) { + return mediaType.startsWith("image") || mediaType.startsWith("text"); + } + } + return false; + } public String getThumbnailUrl() { if (value != null && value.getThumbnail() != null) { diff --git a/app/src/main/java/io/lbry/browser/model/File.java b/app/src/main/java/io/lbry/browser/model/LbryFile.java similarity index 87% rename from app/src/main/java/io/lbry/browser/model/File.java rename to app/src/main/java/io/lbry/browser/model/LbryFile.java index ca68e86..b9df418 100644 --- a/app/src/main/java/io/lbry/browser/model/File.java +++ b/app/src/main/java/io/lbry/browser/model/LbryFile.java @@ -12,7 +12,7 @@ import java.lang.reflect.Type; import lombok.Data; @Data -public class File { +public class LbryFile { private Claim.StreamMetadata metadata; private long addedOn; private int blobsCompleted; @@ -44,11 +44,11 @@ public class File { private String txid; private long writtenBytes; - public static File fromJSONObject(JSONObject fileObject) { + public static LbryFile fromJSONObject(JSONObject fileObject) { String fileJson = fileObject.toString(); - Type type = new TypeToken(){}.getType(); + Type type = new TypeToken(){}.getType(); Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - File file = gson.fromJson(fileJson, type); + LbryFile file = gson.fromJson(fileJson, type); return file; } } diff --git a/app/src/main/java/io/lbry/browser/tasks/ChannelCreateUpdateTask.java b/app/src/main/java/io/lbry/browser/tasks/ChannelCreateUpdateTask.java index 8b9a2ea..7ee1c35 100644 --- a/app/src/main/java/io/lbry/browser/tasks/ChannelCreateUpdateTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/ChannelCreateUpdateTask.java @@ -14,6 +14,7 @@ import java.util.Map; import io.lbry.browser.exceptions.ApiCallException; import io.lbry.browser.model.Claim; +import io.lbry.browser.tasks.claim.ClaimResultHandler; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; @@ -65,7 +66,7 @@ public class ChannelCreateUpdateTask extends AsyncTask { for (int i = 0; i < outputs.length(); i++) { JSONObject output = outputs.getJSONObject(i); if (output.has("claim_id") && output.has("claim_op")) { - claimResult = claimFromResult(output); + claimResult = Claim.claimFromOutput(output); break; } } @@ -77,17 +78,6 @@ public class ChannelCreateUpdateTask extends AsyncTask { return claimResult; } - private static Claim claimFromResult(JSONObject item) { - // we only need name, permanent_url, txid and nout - Claim claim = new Claim(); - claim.setClaimId(Helper.getJSONString("claim_id", null, item)); - claim.setName(Helper.getJSONString("name", null, item)); - claim.setPermanentUrl(Helper.getJSONString("permanent_url", null, item)); - claim.setTxid(Helper.getJSONString("txid", null, item)); - claim.setNout(Helper.getJSONInt("nout", -1, item)); - return claim; - } - protected void onPostExecute(Claim result) { Helper.setViewVisibility(progressView, View.GONE); if (handler != null) { diff --git a/app/src/main/java/io/lbry/browser/tasks/LighthouseSearchTask.java b/app/src/main/java/io/lbry/browser/tasks/LighthouseSearchTask.java index 7003399..ae3f907 100644 --- a/app/src/main/java/io/lbry/browser/tasks/LighthouseSearchTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/LighthouseSearchTask.java @@ -9,6 +9,7 @@ import java.util.List; import io.lbry.browser.exceptions.LbryRequestException; import io.lbry.browser.exceptions.LbryResponseException; import io.lbry.browser.model.Claim; +import io.lbry.browser.tasks.claim.ClaimSearchTask; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lighthouse; diff --git a/app/src/main/java/io/lbry/browser/tasks/ClaimListResultHandler.java b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimListResultHandler.java similarity index 83% rename from app/src/main/java/io/lbry/browser/tasks/ClaimListResultHandler.java rename to app/src/main/java/io/lbry/browser/tasks/claim/ClaimListResultHandler.java index 2f4ce40..2d427e8 100644 --- a/app/src/main/java/io/lbry/browser/tasks/ClaimListResultHandler.java +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimListResultHandler.java @@ -1,4 +1,4 @@ -package io.lbry.browser.tasks; +package io.lbry.browser.tasks.claim; import java.util.List; diff --git a/app/src/main/java/io/lbry/browser/tasks/ClaimListTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimListTask.java similarity index 98% rename from app/src/main/java/io/lbry/browser/tasks/ClaimListTask.java rename to app/src/main/java/io/lbry/browser/tasks/claim/ClaimListTask.java index 67a33d3..90fa345 100644 --- a/app/src/main/java/io/lbry/browser/tasks/ClaimListTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimListTask.java @@ -1,4 +1,4 @@ -package io.lbry.browser.tasks; +package io.lbry.browser.tasks.claim; import android.os.AsyncTask; import android.view.View; diff --git a/app/src/main/java/io/lbry/browser/tasks/ClaimResultHandler.java b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimResultHandler.java similarity index 82% rename from app/src/main/java/io/lbry/browser/tasks/ClaimResultHandler.java rename to app/src/main/java/io/lbry/browser/tasks/claim/ClaimResultHandler.java index deb0544..5171486 100644 --- a/app/src/main/java/io/lbry/browser/tasks/ClaimResultHandler.java +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimResultHandler.java @@ -1,4 +1,4 @@ -package io.lbry.browser.tasks; +package io.lbry.browser.tasks.claim; import io.lbry.browser.model.Claim; diff --git a/app/src/main/java/io/lbry/browser/tasks/ClaimSearchTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimSearchTask.java similarity index 97% rename from app/src/main/java/io/lbry/browser/tasks/ClaimSearchTask.java rename to app/src/main/java/io/lbry/browser/tasks/claim/ClaimSearchTask.java index f8bc710..e480d5d 100644 --- a/app/src/main/java/io/lbry/browser/tasks/ClaimSearchTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ClaimSearchTask.java @@ -1,4 +1,4 @@ -package io.lbry.browser.tasks; +package io.lbry.browser.tasks.claim; import android.os.AsyncTask; import android.view.View; diff --git a/app/src/main/java/io/lbry/browser/tasks/ResolveTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java similarity index 94% rename from app/src/main/java/io/lbry/browser/tasks/ResolveTask.java rename to app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java index 52e3725..5cab8c9 100644 --- a/app/src/main/java/io/lbry/browser/tasks/ResolveTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java @@ -1,4 +1,4 @@ -package io.lbry.browser.tasks; +package io.lbry.browser.tasks.claim; import android.os.AsyncTask; import android.view.View; @@ -8,6 +8,7 @@ import java.util.List; import io.lbry.browser.exceptions.ApiCallException; import io.lbry.browser.model.Claim; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/StreamRepostTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/StreamRepostTask.java new file mode 100644 index 0000000..a1fbed8 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/claim/StreamRepostTask.java @@ -0,0 +1,75 @@ +package io.lbry.browser.tasks.claim; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class StreamRepostTask extends AsyncTask { + private String name; + private BigDecimal bid; + private String claimId; + private String channelId; + private View progressView; + private ClaimResultHandler handler; + private Exception error; + + public StreamRepostTask(String name, BigDecimal bid, String claimId, String channelId, View progressView, ClaimResultHandler handler) { + this.name = name; + this.bid = bid; + this.claimId = claimId; + this.channelId = channelId; + this.progressView = progressView; + this.handler = handler; + } + + protected Claim doInBackground(Void... params) { + Claim claimResult = null; + try { + Map options = new HashMap<>(); + options.put("name", name); + options.put("bid", new DecimalFormat(Helper.SDK_AMOUNT_FORMAT).format(bid.doubleValue())); + options.put("claim_id", claimId); + options.put("channel_id", channelId); + + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_STREAM_REPOST, options); + if (result.has("outputs")) { + JSONArray outputs = result.getJSONArray("outputs"); + for (int i = 0; i < outputs.length(); i++) { + JSONObject output = outputs.getJSONObject(i); + if (output.has("claim_id") && output.has("claim_op")) { + claimResult = Claim.claimFromOutput(output); + break; + } + } + } + } catch (ApiCallException | ClassCastException | JSONException ex) { + error = ex; + } + + return claimResult; + } + + protected void onPostExecute(Claim result) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (result != null) { + handler.onSuccess(result); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/file/DeleteFileTask.java b/app/src/main/java/io/lbry/browser/tasks/file/DeleteFileTask.java new file mode 100644 index 0000000..ba587bb --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/file/DeleteFileTask.java @@ -0,0 +1,42 @@ +package io.lbry.browser.tasks.file; + +import android.os.AsyncTask; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.tasks.GenericTaskHandler; +import io.lbry.browser.utils.Lbry; + +public class DeleteFileTask extends AsyncTask { + private String claimId; + private Exception error; + private GenericTaskHandler handler; + + public DeleteFileTask(String claimId, GenericTaskHandler handler) { + this.claimId = claimId; + this.handler = handler; + } + + protected Boolean doInBackground(Void... params) { + try { + Map options = new HashMap<>(); + options.put("claim_id", claimId); + options.put("delete_from_download_dir", true); + return (boolean) Lbry.genericApiCall(Lbry.METHOD_FILE_DELETE, options); + } catch (ApiCallException ex) { + return false; + } + } + + protected void onPostExecute(Boolean result) { + if (handler != null) { + if (result) { + handler.onSuccess(); + } else { + handler.onError(error); + } + } + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/FileListTask.java b/app/src/main/java/io/lbry/browser/tasks/file/FileListTask.java similarity index 80% rename from app/src/main/java/io/lbry/browser/tasks/FileListTask.java rename to app/src/main/java/io/lbry/browser/tasks/file/FileListTask.java index c57128b..58ef143 100644 --- a/app/src/main/java/io/lbry/browser/tasks/FileListTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/file/FileListTask.java @@ -1,4 +1,4 @@ -package io.lbry.browser.tasks; +package io.lbry.browser.tasks.file; import android.os.AsyncTask; import android.view.View; @@ -6,11 +6,11 @@ import android.view.View; import java.util.List; import io.lbry.browser.exceptions.ApiCallException; -import io.lbry.browser.model.File; +import io.lbry.browser.model.LbryFile; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; -public class FileListTask extends AsyncTask> { +public class FileListTask extends AsyncTask> { private String claimId; private FileListResultHandler handler; private View progressView; @@ -28,7 +28,7 @@ public class FileListTask extends AsyncTask> { protected void onPreExecute() { Helper.setViewVisibility(progressView, View.VISIBLE); } - protected List doInBackground(Void... params) { + protected List doInBackground(Void... params) { try { return Lbry.fileList(claimId); } catch (ApiCallException ex) { @@ -36,7 +36,7 @@ public class FileListTask extends AsyncTask> { return null; } } - protected void onPostExecute(List files) { + protected void onPostExecute(List files) { Helper.setViewVisibility(progressView, View.GONE); if (handler != null) { if (files != null) { @@ -48,7 +48,7 @@ public class FileListTask extends AsyncTask> { } public interface FileListResultHandler { - void onSuccess(List files); + void onSuccess(List files); void onError(Exception error); } } diff --git a/app/src/main/java/io/lbry/browser/tasks/file/GetFileTask.java b/app/src/main/java/io/lbry/browser/tasks/file/GetFileTask.java new file mode 100644 index 0000000..6460195 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/file/GetFileTask.java @@ -0,0 +1,72 @@ +package io.lbry.browser.tasks.file; + +import android.os.AsyncTask; +import android.view.View; + +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.model.LbryFile; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; + +public class GetFileTask extends AsyncTask { + private String uri; + private boolean saveFile; + private View progressView; + private GetFileHandler handler; + private Exception error; + + public GetFileTask(String uri, boolean saveFile, View progressView, GetFileHandler handler) { + this.uri = uri; + this.saveFile = saveFile; + this.progressView = progressView; + this.handler = handler; + } + + protected void onPreExecute() { + Helper.setViewVisibility(progressView, View.VISIBLE); + if (handler != null) { + handler.beforeStart(); + } + } + + protected LbryFile doInBackground(Void... params) { + LbryFile file = null; + try { + Map options = new HashMap<>(); + options.put("uri", uri); + options.put("save_file", saveFile); + JSONObject streamInfo = (JSONObject) Lbry.genericApiCall("get", options); + if (streamInfo.has("error")) { + throw new ApiCallException(Helper.getJSONString("error", "", streamInfo)); + } + + file = LbryFile.fromJSONObject(streamInfo); + } catch (ApiCallException ex) { + error = ex; + } + + return file; + } + + protected void onPostExecute(LbryFile file) { + Helper.setViewVisibility(progressView, View.GONE); + if (handler != null) { + if (file != null) { + handler.onSuccess(file, saveFile); + } else { + handler.onError(error); + } + } + } + + public interface GetFileHandler { + void beforeStart(); + void onSuccess(LbryFile file, boolean saveFile); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/allcontent/AllContentFragment.java b/app/src/main/java/io/lbry/browser/ui/allcontent/AllContentFragment.java index 64b9fd5..9eb165e 100644 --- a/app/src/main/java/io/lbry/browser/ui/allcontent/AllContentFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/allcontent/AllContentFragment.java @@ -33,7 +33,7 @@ import io.lbry.browser.dialog.CustomizeTagsDialogFragment; import io.lbry.browser.listener.TagListener; import io.lbry.browser.model.Claim; import io.lbry.browser.model.Tag; -import io.lbry.browser.tasks.ClaimSearchTask; +import io.lbry.browser.tasks.claim.ClaimSearchTask; import io.lbry.browser.tasks.FollowUnfollowTagTask; import io.lbry.browser.ui.BaseFragment; import io.lbry.browser.utils.Helper; diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelContentFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelContentFragment.java index 542ee2e..ad4c337 100644 --- a/app/src/main/java/io/lbry/browser/ui/channel/ChannelContentFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelContentFragment.java @@ -28,7 +28,7 @@ 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.tasks.claim.ClaimSearchTask; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; import io.lbry.browser.utils.Predefined; diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFormFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFormFragment.java index d546a1e..926143d 100644 --- a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFormFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFormFragment.java @@ -45,7 +45,7 @@ import io.lbry.browser.model.WalletBalance; import io.lbry.browser.tasks.UpdateSuggestedTagsTask; import io.lbry.browser.tasks.UploadImageTask; import io.lbry.browser.tasks.ChannelCreateUpdateTask; -import io.lbry.browser.tasks.ClaimResultHandler; +import io.lbry.browser.tasks.claim.ClaimResultHandler; import io.lbry.browser.tasks.lbryinc.LogPublishTask; import io.lbry.browser.ui.BaseFragment; import io.lbry.browser.utils.Helper; diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java index 73f17a5..d08d253 100644 --- a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java @@ -37,8 +37,8 @@ import io.lbry.browser.listener.FetchChannelsListener; import io.lbry.browser.model.Claim; import io.lbry.browser.model.lbryinc.Subscription; import io.lbry.browser.tasks.lbryinc.ChannelSubscribeTask; -import io.lbry.browser.tasks.ClaimListResultHandler; -import io.lbry.browser.tasks.ResolveTask; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ResolveTask; import io.lbry.browser.tasks.lbryinc.FetchStatCountTask; import io.lbry.browser.ui.BaseFragment; import io.lbry.browser.ui.controls.SolidIconView; diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelManagerFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelManagerFragment.java index 0d0207e..62c1b38 100644 --- a/app/src/main/java/io/lbry/browser/ui/channel/ChannelManagerFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelManagerFragment.java @@ -10,11 +10,9 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; -import android.widget.LinearLayout; import android.widget.ProgressBar; import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.view.ActionMode; import androidx.recyclerview.widget.LinearLayoutManager; @@ -22,7 +20,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.floatingactionbutton.FloatingActionButton; -import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -35,8 +32,8 @@ import io.lbry.browser.listener.SdkStatusListener; import io.lbry.browser.listener.SelectionModeListener; import io.lbry.browser.model.Claim; import io.lbry.browser.model.NavMenuItem; -import io.lbry.browser.tasks.ClaimListResultHandler; -import io.lbry.browser.tasks.ClaimListTask; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimListTask; import io.lbry.browser.ui.BaseFragment; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; diff --git a/app/src/main/java/io/lbry/browser/ui/editorschoice/EditorsChoiceFragment.java b/app/src/main/java/io/lbry/browser/ui/editorschoice/EditorsChoiceFragment.java index 8cfa9b3..25a0649 100644 --- a/app/src/main/java/io/lbry/browser/ui/editorschoice/EditorsChoiceFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/editorschoice/EditorsChoiceFragment.java @@ -25,17 +25,14 @@ 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.adapter.EditorsChoiceItemAdapter; -import io.lbry.browser.dialog.ContentScopeDialogFragment; import io.lbry.browser.model.Claim; import io.lbry.browser.model.EditorsChoiceItem; -import io.lbry.browser.tasks.ClaimSearchTask; +import io.lbry.browser.tasks.claim.ClaimSearchTask; import io.lbry.browser.ui.BaseFragment; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; import io.lbry.browser.utils.LbryAnalytics; -import io.lbry.browser.utils.Predefined; public class EditorsChoiceFragment extends BaseFragment { diff --git a/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java b/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java index 8a9b8fd..be5f3fa 100644 --- a/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java @@ -38,10 +38,10 @@ import io.lbry.browser.exceptions.LbryUriException; import io.lbry.browser.model.Claim; import io.lbry.browser.model.lbryinc.Subscription; import io.lbry.browser.tasks.lbryinc.ChannelSubscribeTask; -import io.lbry.browser.tasks.ClaimListResultHandler; -import io.lbry.browser.tasks.ClaimSearchTask; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimSearchTask; import io.lbry.browser.tasks.lbryinc.FetchSubscriptionsTask; -import io.lbry.browser.tasks.ResolveTask; +import io.lbry.browser.tasks.claim.ResolveTask; import io.lbry.browser.listener.ChannelItemSelectionListener; import io.lbry.browser.ui.BaseFragment; import io.lbry.browser.utils.Helper; diff --git a/app/src/main/java/io/lbry/browser/ui/other/AboutFragment.java b/app/src/main/java/io/lbry/browser/ui/other/AboutFragment.java new file mode 100644 index 0000000..bf7e753 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/ui/other/AboutFragment.java @@ -0,0 +1,245 @@ +package io.lbry.browser.ui.other; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.appcompat.app.ActionBar; +import androidx.core.content.FileProvider; +import androidx.preference.PreferenceManager; + +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.android.material.snackbar.Snackbar; +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.InstanceIdResult; + +import org.json.JSONObject; + +import java.io.File; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.listener.SdkStatusListener; +import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.LbryAnalytics; +import io.lbry.browser.utils.Lbryio; +import io.lbry.lbrysdk.Utils; + +public class AboutFragment extends BaseFragment implements SdkStatusListener { + + private static final String FILE_PROVIDER = "io.lbry.browser.fileprovider"; + + private TextView textLinkWhatIsLBRY; + private TextView textLinkAndroidBasics; + private TextView textLinkFAQ; + private TextView textLinkDiscord; + private TextView textLinkFacebook; + private TextView textLinkInstagram; + private TextView textLinkReddit; + private TextView textLinkTelegram; + private TextView textLinkTwitter; + + private TextView textConnectedEmail; + private TextView textAppVersion; + private TextView textLbrySdkVersion; + private TextView textPlatform; + private TextView textInstallationId; + private TextView textFirebaseToken; + private View linkSendLog; + private View linkUpdateMailingPreferences; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_about, container, false); + + textLinkWhatIsLBRY = root.findViewById(R.id.about_link_what_is_lbry); + textLinkAndroidBasics = root.findViewById(R.id.about_link_android_basics); + textLinkFAQ = root.findViewById(R.id.about_link_faq); + textLinkDiscord = root.findViewById(R.id.about_link_discord); + textLinkFacebook = root.findViewById(R.id.about_link_facebook); + textLinkInstagram = root.findViewById(R.id.about_link_instagram); + textLinkReddit = root.findViewById(R.id.about_link_reddit); + textLinkTelegram = root.findViewById(R.id.about_link_telegram); + textLinkTwitter = root.findViewById(R.id.about_link_twitter); + + TextView[] textLinks = { + textLinkWhatIsLBRY, textLinkAndroidBasics, textLinkFAQ, textLinkDiscord, textLinkFacebook, + textLinkInstagram, textLinkReddit, textLinkTelegram, textLinkTwitter + }; + for (TextView view : textLinks) { + Helper.applyHtmlForTextView(view); + } + + textConnectedEmail = root.findViewById(R.id.about_connected_email); + textAppVersion = root.findViewById(R.id.about_app_version); + textLbrySdkVersion = root.findViewById(R.id.about_lbry_sdk); + textPlatform = root.findViewById(R.id.about_platform); + textInstallationId = root.findViewById(R.id.about_installation_id); + textFirebaseToken = root.findViewById(R.id.about_firebase_token); + linkSendLog = root.findViewById(R.id.about_send_log); + linkUpdateMailingPreferences = root.findViewById(R.id.about_update_mailing_preferences); + + if (Lbryio.isSignedIn()) { + textConnectedEmail.setText(Lbryio.getSignedInEmail()); + textConnectedEmail.setTypeface(null, Typeface.NORMAL); + linkUpdateMailingPreferences.setVisibility(View.VISIBLE); + } else { + linkUpdateMailingPreferences.setVisibility(View.GONE); + } + + Context context = getContext(); + String appVersion = getString(R.string.unknown); + if (context != null) { + try { + PackageManager manager = context.getPackageManager(); + PackageInfo info = manager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES); + appVersion = info.versionName; + } catch (PackageManager.NameNotFoundException ex) { + // pass + } + } + textAppVersion.setText(appVersion); + textInstallationId.setText(Lbry.INSTALLATION_ID); + textPlatform.setText(String.format("Android %s (API %d)", Utils.getAndroidRelease(), Utils.getAndroidSdk())); + + linkUpdateMailingPreferences.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("http://lbry.com/list/edit/%s", Lbryio.AUTH_TOKEN))); + startActivity(intent); + } + }); + + linkSendLog.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + shareLogFile(); + } + }); + + return root; + } + + private void shareLogFile() { + Context context = getContext(); + if (context != null) { + String logFileName = "lbrynet.log"; + File logFile = new File(String.format("%s/%s", Utils.getAppInternalStorageDir(context), "lbrynet"), logFileName); + if (!logFile.exists()) { + Snackbar.make(getView(), R.string.cannot_find_lbrynet_log, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED). + setTextColor(Color.WHITE). + show(); + return; + } + + try { + Uri fileUri = FileProvider.getUriForFile(getContext(), FILE_PROVIDER, logFile); + if (fileUri != null) { + MainActivity.startingShareActivity = true; + Intent shareIntent = new Intent(); + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.setType("text/plain"); + shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri); + + Intent sendLogIntent = Intent.createChooser(shareIntent, "Send LBRY log"); + sendLogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(sendLogIntent); + } + } catch (IllegalArgumentException e) { + Snackbar.make(getView(), R.string.cannot_share_lbrynet_log, Snackbar.LENGTH_LONG). + setBackgroundTint(Color.RED). + setTextColor(Color.WHITE). + show(); + } + } + } + + @Override + public void onStart() { + super.onStart(); + MainActivity activity = (MainActivity) getContext(); + if (activity != null) { + activity.hideSearchBar(); + activity.showNavigationBackIcon(); + activity.lockDrawer(); + activity.hideFloatingWalletBalance(); + + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(R.string.about_lbry); + } + } + } + + @Override + public void onResume() { + super.onResume(); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + LbryAnalytics.setCurrentScreen(activity, "Settings", "Settings"); + + if (!Lbry.SDK_READY) { + activity.addSdkStatusListener(this); + } else { + onSdkReady(); + } + } + FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(Task task) { + Helper.setViewText(textFirebaseToken, task.isSuccessful() ? task.getResult().getToken() : getString(R.string.unknown)); + } + }); + + } + + @Override + public void onStop() { + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) getContext(); + activity.removeSdkStatusListener(this); + activity.restoreToggle(); + activity.showFloatingWalletBalance(); + } + super.onStop(); + } + + public void onSdkReady() { + loadLbryVersion(); + } + + private void loadLbryVersion() { + (new AsyncTask() { + protected String doInBackground(Void... params) { + try { + JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_VERSION); + return Helper.getJSONString("lbrynet_version", null, result); + } catch (ApiCallException | ClassCastException ex) { + // pass + return null; + } + } + protected void onPostExecute(String version) { + Helper.setViewText(textLbrySdkVersion, Helper.isNullOrEmpty(version) ? getString(R.string.unknown) : version); + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/settings/SettingsFragment.java b/app/src/main/java/io/lbry/browser/ui/other/SettingsFragment.java similarity index 98% rename from app/src/main/java/io/lbry/browser/ui/settings/SettingsFragment.java rename to app/src/main/java/io/lbry/browser/ui/other/SettingsFragment.java index 65a7377..4ac2b59 100644 --- a/app/src/main/java/io/lbry/browser/ui/settings/SettingsFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/other/SettingsFragment.java @@ -1,4 +1,4 @@ -package io.lbry.browser.ui.settings; +package io.lbry.browser.ui.other; import android.content.Context; import android.content.SharedPreferences; diff --git a/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java b/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java index 208e8b9..feeb654 100644 --- a/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java @@ -22,10 +22,10 @@ import io.lbry.browser.R; import io.lbry.browser.adapter.ClaimListAdapter; import io.lbry.browser.model.Claim; import io.lbry.browser.model.ClaimCacheKey; -import io.lbry.browser.tasks.ClaimListResultHandler; -import io.lbry.browser.tasks.ClaimSearchTask; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimSearchTask; import io.lbry.browser.tasks.LighthouseSearchTask; -import io.lbry.browser.tasks.ResolveTask; +import io.lbry.browser.tasks.claim.ResolveTask; import io.lbry.browser.ui.BaseFragment; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; @@ -129,6 +129,7 @@ public class SearchFragment extends BaseFragment implements claim.setName(query); claim.setFeatured(true); claim.setUnresolved(true); + claim.setConfirmations(1); return claim; } @@ -150,7 +151,7 @@ public class SearchFragment extends BaseFragment implements ResolveTask task = new ResolveTask(vanityUrl, Lbry.LBRY_TV_CONNECTION_STRING, null, new ClaimListResultHandler() { @Override public void onSuccess(List claims) { - if (claims.size() > 0) { + if (claims.size() > 0 && !Helper.isNullOrEmpty(claims.get(0).getClaimId())) { Claim resolved = claims.get(0); Lbry.claimCache.put(key, resolved); updateFeaturedItemFromResolvedClaim(resolved); diff --git a/app/src/main/java/io/lbry/browser/ui/wallet/InvitesFragment.java b/app/src/main/java/io/lbry/browser/ui/wallet/InvitesFragment.java index 0d1a906..b1781b2 100644 --- a/app/src/main/java/io/lbry/browser/ui/wallet/InvitesFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/wallet/InvitesFragment.java @@ -38,11 +38,11 @@ import io.lbry.browser.listener.WalletBalanceListener; import io.lbry.browser.model.Claim; import io.lbry.browser.model.WalletBalance; import io.lbry.browser.model.lbryinc.Invitee; -import io.lbry.browser.tasks.ClaimListResultHandler; -import io.lbry.browser.tasks.ClaimListTask; +import io.lbry.browser.tasks.claim.ClaimListResultHandler; +import io.lbry.browser.tasks.claim.ClaimListTask; import io.lbry.browser.tasks.GenericTaskHandler; import io.lbry.browser.tasks.ChannelCreateUpdateTask; -import io.lbry.browser.tasks.ClaimResultHandler; +import io.lbry.browser.tasks.claim.ClaimResultHandler; import io.lbry.browser.tasks.lbryinc.LogPublishTask; import io.lbry.browser.tasks.lbryinc.FetchInviteStatusTask; import io.lbry.browser.tasks.lbryinc.FetchReferralCodeTask; diff --git a/app/src/main/java/io/lbry/browser/utils/ExoplayerAudioRenderer.java b/app/src/main/java/io/lbry/browser/utils/ExoplayerAudioRenderer.java new file mode 100644 index 0000000..832db7f --- /dev/null +++ b/app/src/main/java/io/lbry/browser/utils/ExoplayerAudioRenderer.java @@ -0,0 +1,53 @@ +package io.lbry.browser.utils; + +import android.content.Context; +import android.os.Handler; + +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.DefaultRenderersFactory; +import com.google.android.exoplayer2.Renderer; +import com.google.android.exoplayer2.audio.AudioProcessor; +import com.google.android.exoplayer2.audio.AudioRendererEventListener; +import com.google.android.exoplayer2.audio.TeeAudioProcessor; +import com.google.android.exoplayer2.drm.DrmSessionManager; +import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; +import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; + +import java.util.ArrayList; + +public class ExoplayerAudioRenderer extends DefaultRenderersFactory { + + private TeeAudioProcessor.AudioBufferSink audioBufferSink; + + public ExoplayerAudioRenderer(Context context, TeeAudioProcessor.AudioBufferSink audioBufferSink) { + super(context); + this.audioBufferSink = audioBufferSink; + } + + @Override + protected void buildAudioRenderers( + Context context, + int extensionRendererMode, + MediaCodecSelector mediaCodecSelector, + @Nullable DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys, + boolean enableDecoderFallback, + AudioProcessor[] audioProcessors, + Handler eventHandler, + AudioRendererEventListener eventListener, + ArrayList out) { + AudioProcessor[] audioProcessorList = { new TeeAudioProcessor(audioBufferSink) }; + super.buildAudioRenderers( + context, + extensionRendererMode, + mediaCodecSelector, + drmSessionManager, + playClearSamplesWithoutKeys, + enableDecoderFallback, + audioProcessorList, + eventHandler, + eventListener, + out); + } +} diff --git a/app/src/main/java/io/lbry/browser/utils/Lbry.java b/app/src/main/java/io/lbry/browser/utils/Lbry.java index 680f695..3c2117c 100644 --- a/app/src/main/java/io/lbry/browser/utils/Lbry.java +++ b/app/src/main/java/io/lbry/browser/utils/Lbry.java @@ -1,18 +1,10 @@ package io.lbry.browser.utils; -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.reflect.TypeToken; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; -import java.lang.reflect.Type; import java.security.KeyStore; import java.util.ArrayList; import java.util.Arrays; @@ -29,12 +21,10 @@ import io.lbry.browser.exceptions.LbryResponseException; import io.lbry.browser.model.Claim; import io.lbry.browser.model.ClaimCacheKey; import io.lbry.browser.model.ClaimSearchCacheValue; -import io.lbry.browser.model.File; +import io.lbry.browser.model.LbryFile; import io.lbry.browser.model.Tag; import io.lbry.browser.model.Transaction; import io.lbry.browser.model.WalletBalance; -import io.lbry.browser.model.lbryinc.Reward; -import io.lbry.browser.model.lbryinc.User; import io.lbry.lbrysdk.Utils; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -68,11 +58,13 @@ public final class Lbry { public static final String METHOD_RESOLVE = "resolve"; public static final String METHOD_CLAIM_SEARCH = "claim_search"; public static final String METHOD_FILE_LIST = "file_list"; + public static final String METHOD_FILE_DELETE = "file_delete"; public static final String METHOD_GET = "get"; public static final String METHOD_WALLET_BALANCE = "wallet_balance"; public static final String METHOD_WALLET_ENCRYPT = "wallet_encrypt"; public static final String METHOD_WALLET_DECRYPT = "wallet_decrypt"; + public static final String METHOD_VERSION = "version"; public static final String METHOD_WALLET_LIST = "wallet_list"; public static final String METHOD_WALLET_SEND = "wallet_send"; @@ -95,6 +87,7 @@ public final class Lbry { public static final String METHOD_CHANNEL_UPDATE = "channel_update"; public static final String METHOD_CLAIM_LIST = "claim_list"; + public static final String METHOD_STREAM_REPOST = "stream_repost"; public static KeyStore KEYSTORE; public static boolean SDK_READY = false; @@ -205,7 +198,7 @@ public final class Lbry { } else { errorMessage = ((JSONObject) jsonError).getString("message"); } - throw new LbryResponseException(json.getString("error")); + throw new LbryResponseException(!Helper.isNullOrEmpty(errorMessage) ? errorMessage : json.getString("error")); } else { throw new LbryResponseException("Protocol error with unknown response signature."); } @@ -284,13 +277,13 @@ public final class Lbry { return transactions; } - public static File get(boolean saveFile) throws ApiCallException { - File file = null; + public static LbryFile get(boolean saveFile) throws ApiCallException { + LbryFile file = null; Map params = new HashMap<>(); params.put("save_file", saveFile); try { JSONObject result = (JSONObject) parseResponse(apiCall(METHOD_GET, params)); - file = File.fromJSONObject(result); + file = LbryFile.fromJSONObject(result); if (file != null) { String fileClaimId = file.getClaimId(); @@ -309,8 +302,8 @@ public final class Lbry { return file; } - public static List fileList(String claimId) throws ApiCallException { - List files = new ArrayList<>(); + public static List fileList(String claimId) throws ApiCallException { + List files = new ArrayList<>(); Map params = new HashMap<>(); if (!Helper.isNullOrEmpty(claimId)) { params.put("claim_id", claimId); @@ -320,7 +313,7 @@ public final class Lbry { JSONArray items = result.getJSONArray("items"); for (int i = 0; i < items.length(); i++) { JSONObject fileObject = items.getJSONObject(i); - File file = File.fromJSONObject(fileObject); + LbryFile file = LbryFile.fromJSONObject(fileObject); files.add(file); String fileClaimId = file.getClaimId(); 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 6262d86..12fab55 100644 --- a/app/src/main/java/io/lbry/browser/utils/Lbryio.java +++ b/app/src/main/java/io/lbry/browser/utils/Lbryio.java @@ -43,6 +43,9 @@ import okhttp3.Response; @Data public final class Lbryio { + // TODO: Get this from the bundled aar + public static String SDK_VERSION = "0.73.1"; + public static User currentUser; public static boolean userHasSyncedWallet = false; public static String lastRemoteHash; @@ -212,7 +215,7 @@ public final class Lbryio { options.put("app_version", appVersion); options.put("app_id", Lbry.INSTALLATION_ID); options.put("node_id", ""); - options.put("daemon_version", "0.67.1"); + options.put("daemon_version", SDK_VERSION); options.put("operating_system", "android"); options.put("platform", String.format("Android %s (API %d)", Utils.getAndroidRelease(), Utils.getAndroidSdk())); try { diff --git a/app/src/main/res/drawable-anydpi/ic_stop.xml b/app/src/main/res/drawable-anydpi/ic_stop.xml new file mode 100644 index 0000000..34bc28f --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_stop.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/ic_stop.png b/app/src/main/res/drawable-hdpi/ic_stop.png new file mode 100644 index 0000000..b9164b8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stop.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_stop.png b/app/src/main/res/drawable-mdpi/ic_stop.png new file mode 100644 index 0000000..307fe3a Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stop.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stop.png b/app/src/main/res/drawable-xhdpi/ic_stop.png new file mode 100644 index 0000000..3db50c5 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stop.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_stop.png b/app/src/main/res/drawable-xxhdpi/ic_stop.png new file mode 100644 index 0000000..98e9f13 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_stop.png differ diff --git a/app/src/main/res/drawable/determinate_progress_circle.xml b/app/src/main/res/drawable/determinate_progress_circle.xml new file mode 100644 index 0000000..6a3b230 --- /dev/null +++ b/app/src/main/res/drawable/determinate_progress_circle.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_file_view.xml b/app/src/main/res/layout/activity_file_view.xml index 93619cd..29bfa6f 100644 --- a/app/src/main/res/layout/activity_file_view.xml +++ b/app/src/main/res/layout/activity_file_view.xml @@ -42,7 +42,7 @@ android:layout_weight="10" android:layout_width="match_parent" android:layout_height="match_parent" - android:visibility="invisible"> + android:visibility="visible"> + + android:text="@string/fa_coins" + android:textColor="@android:color/black" /> @@ -108,10 +116,17 @@ android:layout_width="match_parent" android:layout_height="match_parent" app:controller_layout_id="@layout/exo_playback_control_view"/> + - + + + - + + + - + + + - + android:visibility="gone"> + + + + + + + + + + + - + + + - - - - - - + + + + android:layout_height="0.5dp" /> - + + android:layout_height="0.5dp" /> @@ -499,7 +561,7 @@ android:layout_width="match_parent" android:layout_marginTop="12dp" android:layout_marginBottom="12dp" - android:layout_height="1dp" /> + android:layout_height="0.5dp" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml index 35955a6..ba9ed15 100644 --- a/app/src/main/res/layout/app_bar_main.xml +++ b/app/src/main/res/layout/app_bar_main.xml @@ -36,6 +36,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/uri_placeholder" + android:imeOptions="actionGo" android:selectAllOnFocus="true" android:singleLine="true" android:textFontWeight="300" diff --git a/app/src/main/res/layout/card_wallet_balance.xml b/app/src/main/res/layout/card_wallet_balance.xml index 0653678..3468bda 100644 --- a/app/src/main/res/layout/card_wallet_balance.xml +++ b/app/src/main/res/layout/card_wallet_balance.xml @@ -77,8 +77,9 @@ android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:fontFamily="@font/inter" - android:textSize="12sp" - android:text="@string/convert_credits" /> + android:text="@string/convert_credits" + android:textColorLink="@color/lbryGreen" + android:textSize="12sp" /> + android:text="@string/convert_credits_bittrex" + android:textColorLink="@color/lbryGreen" + android:textSize="16sp" /> diff --git a/app/src/main/res/layout/card_wallet_recent_transactions.xml b/app/src/main/res/layout/card_wallet_recent_transactions.xml index 0bee6c1..3a67e1d 100644 --- a/app/src/main/res/layout/card_wallet_recent_transactions.xml +++ b/app/src/main/res/layout/card_wallet_recent_transactions.xml @@ -41,7 +41,7 @@ @@ -115,14 +115,16 @@ android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="@string/manual_backup" /> + android:text="@string/manual_backup" + android:textColorLink="@color/lbryGreen" /> + android:text="@string/sync_faq" + android:textColorLink="@color/lbryGreen"/> \ No newline at end of file diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index f87abc3..7a78d48 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -31,6 +31,36 @@ + + + + + + + + app:layout_constraintBottom_toTopOf="@id/global_sdk_initializing_status"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_send_tip.xml b/app/src/main/res/layout/dialog_send_tip.xml index c5a034b..6912778 100644 --- a/app/src/main/res/layout/dialog_send_tip.xml +++ b/app/src/main/res/layout/dialog_send_tip.xml @@ -19,7 +19,7 @@ android:textSize="20sp" /> diff --git a/app/src/main/res/layout/exo_playback_control_view.xml b/app/src/main/res/layout/exo_playback_control_view.xml index c3adccf..dff3ceb 100644 --- a/app/src/main/res/layout/exo_playback_control_view.xml +++ b/app/src/main/res/layout/exo_playback_control_view.xml @@ -8,25 +8,18 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_channel_form.xml b/app/src/main/res/layout/fragment_channel_form.xml index 01a3740..8ee27cf 100644 --- a/app/src/main/res/layout/fragment_channel_form.xml +++ b/app/src/main/res/layout/fragment_channel_form.xml @@ -294,7 +294,7 @@ android:layout_height="wrap_content" android:layout_centerVertical="true" android:fontFamily="@font/inter" - android:textSize="12sp" + android:textSize="14sp" android:text="@string/cancel" /> diff --git a/app/src/main/res/layout/fragment_verification_phone.xml b/app/src/main/res/layout/fragment_verification_phone.xml index 2010f9c..6048ef3 100644 --- a/app/src/main/res/layout/fragment_verification_phone.xml +++ b/app/src/main/res/layout/fragment_verification_phone.xml @@ -53,7 +53,6 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:background="@android:color/transparent" - android:paddingTop="2dp" android:fontFamily="@font/inter" android:singleLine="true" android:textSize="20sp" diff --git a/app/src/main/res/layout/list_item_featured_search_result.xml b/app/src/main/res/layout/list_item_featured_search_result.xml index 6c45038..0804a85 100644 --- a/app/src/main/res/layout/list_item_featured_search_result.xml +++ b/app/src/main/res/layout/list_item_featured_search_result.xml @@ -106,7 +106,8 @@ android:layout_height="12dp" android:layout_gravity="center_vertical" android:textSize="8dp" - android:text="@string/fa_coins" /> + android:text="@string/fa_coins" + android:textColor="@android:color/black"/> diff --git a/app/src/main/res/layout/list_item_stream.xml b/app/src/main/res/layout/list_item_stream.xml index 635ac17..0085541 100644 --- a/app/src/main/res/layout/list_item_stream.xml +++ b/app/src/main/res/layout/list_item_stream.xml @@ -110,7 +110,8 @@ android:layout_height="12dp" android:layout_gravity="center_vertical" android:textSize="8dp" - android:text="@string/fa_coins" /> + android:text="@string/fa_coins" + android:textColor="@android:color/black"/> diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layout/nav_header_main.xml index dbdc2cd..d33feba 100644 --- a/app/src/main/res/layout/nav_header_main.xml +++ b/app/src/main/res/layout/nav_header_main.xml @@ -15,6 +15,7 @@ android:layout_gravity="bottom" android:clickable="true" android:paddingTop="38dp" + android:foreground="?attr/selectableItemBackground" android:background="@color/lbryGreen"> + android:layout_height="0.5dp" /> diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index df610a7..1dca2ec 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -52,7 +52,7 @@ #FF4A7D #26BCF7 - #D5D5D5 + #252525 #CAEDB9 #CC333333 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ec5bf9a..cf73086 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -55,22 +55,32 @@ Edit Delete Download + Open Report Loading decentralized data... Related Content Share LBRY content View Play - - %1$s view - %1$s views - Unsupported Content - Sorry, we are unable to display this content in the app. You can find the file named %1$s in your downloads folder. + Sorry, we are unable to display this content in the app. You can find the file %1$sin your downloads folder. There\'s nothing at this location. Publish something here This content cannot be accessed at this time. Please try again later. 00:00 + The file at "%1$s" does not exist. + Confirm Purchase + Delete file + Are you sure you want to remove this file from your device? + Failed to load %1$s. Please try again later. + + %1$s view + %1$s views + + + This will purchase "%1$s" for %2$s credit + This will purchase "%1$s" for %2$s credits + There\'s nothing here yet.\nPlease check back later. @@ -215,7 +225,15 @@ This will appear as a tip for %1$s, which will boost its ability to be discovered while active. <a href="https://lbry.com/faq/tipping">Learn more</a>. This will appear as a tip for %1$s, which will boost the channel\'s ability to be discovered while active. <a href="https://lbry.com/faq/tipping">Learn more</a>. Cancel - + Repost %1$s + Repost your favorite content to help more people discover them! + Channel to post on + Show advanced + Hide advanced + Name + 0.01 + The content was successfully reposted! + The repost name contains invalid characters. You sent %1$s credit as a tip, Mahalo! You sent %1$s credits as a tip, Mahalo! @@ -344,6 +362,36 @@ Invite link copied. Invite sent to %1$s + + About LBRY + Content Freedom + LBRY is a free, open, and community-run digital marketplace. It is a decentralized peer-to-peer content distribution platform for creators to upload and share content, and earn LBRY credits for their effort. Users will be able to find a wide selection of videos, music, ebooks and other digital content they are interested in. + Get Social + You can interact with the LBRY team and members of the community on Discord, Facebook, Instagram, Twitter or Reddit. + App info + Loading... + <a href="https://lbry.com/faq/what-is-lbry">What is LBRY?</a> + <a href="https://lbry.com/faq/android-basics">Android Basics</a> + <a href="https://lbry.com/faq">FAQ</a> + <a href="https://discordapp.com/invite/Z3bERWA">Discord</a> + <a href="https://www.facebook.com/LBRYio">Facebook</a> + <a href="https://www.instagram.com/LBRYio">Instagram</a> + <a href="https://reddit.com/r/lbry">Reddit</a> + <a href="https://t.me/lbryofficial">Telegram</a> + <a href="https://twitter.com/LBRYio">Twitter</a> + Update mailing preferences + App version + LBRY SDK + Platform + Installation ID + Firebase Token + Logs + Send log + Connected email + Unknown + The lbrynet.log file could not be found. + The lbrynet.log file cannot be shared due to permission restrictions. + @@ -366,4 +414,5 @@ + diff --git a/app/src/main/res/xml/filepaths.xml b/app/src/main/res/xml/filepaths.xml new file mode 100644 index 0000000..a6fad46 --- /dev/null +++ b/app/src/main/res/xml/filepaths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index ad32875..e14841d 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ allprojects { repositories { google() jcenter() - + maven { url "https://jitpack.io" } } }