Surf mode experiment (#1003)

* surf mode implementation
* occupy entire vertical area
* fix onResume logic
* shuffle mode with selected channel ids
This commit is contained in:
Akinwale Ariwodola 2020-09-18 14:53:19 +01:00 committed by GitHub
parent 722c829502
commit 9b9ef9ab74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 904 additions and 32 deletions

View file

@ -188,9 +188,10 @@ import io.lbry.browser.ui.other.AboutFragment;
import io.lbry.browser.ui.publish.PublishFormFragment; import io.lbry.browser.ui.publish.PublishFormFragment;
import io.lbry.browser.ui.publish.PublishFragment; import io.lbry.browser.ui.publish.PublishFragment;
import io.lbry.browser.ui.publish.PublishesFragment; import io.lbry.browser.ui.publish.PublishesFragment;
import io.lbry.browser.ui.findcontent.SearchFragment;
import io.lbry.browser.ui.other.SettingsFragment;
import io.lbry.browser.ui.findcontent.AllContentFragment; import io.lbry.browser.ui.findcontent.AllContentFragment;
import io.lbry.browser.ui.findcontent.SearchFragment;
import io.lbry.browser.ui.findcontent.ShuffleFragment;
import io.lbry.browser.ui.other.SettingsFragment;
import io.lbry.browser.ui.wallet.InvitesFragment; import io.lbry.browser.ui.wallet.InvitesFragment;
import io.lbry.browser.ui.wallet.RewardsFragment; import io.lbry.browser.ui.wallet.RewardsFragment;
import io.lbry.browser.ui.wallet.WalletFragment; import io.lbry.browser.ui.wallet.WalletFragment;
@ -214,6 +215,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
private static final String SPECIAL_URL_PREFIX = "lbry://?"; private static final String SPECIAL_URL_PREFIX = "lbry://?";
private static final int REMOTE_NOTIFICATION_REFRESH_TTL = 300000; // 5 minutes private static final int REMOTE_NOTIFICATION_REFRESH_TTL = 300000; // 5 minutes
public static final String SKU_SKIP = "lbryskip"; public static final String SKU_SKIP = "lbryskip";
public static final int SOURCE_NOW_PLAYING_FILE = 1;
public static final int SOURCE_NOW_PLAYING_SHUFFLE = 2;
public static MainActivity instance; public static MainActivity instance;
private boolean shuttingDown; private boolean shuttingDown;
@ -232,6 +236,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
public static boolean playerReassigned; public static boolean playerReassigned;
public CastContext castContext; public CastContext castContext;
public static CastPlayer castPlayer; public static CastPlayer castPlayer;
public static int nowPlayingSource;
public static Claim nowPlayingClaim; public static Claim nowPlayingClaim;
public static String nowPlayingClaimUrl; public static String nowPlayingClaimUrl;
public static boolean startingFilePickerActivity = false; public static boolean startingFilePickerActivity = false;
@ -264,6 +269,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
fragmentClassNavIdMap.put(FollowingFragment.class, NavMenuItem.ID_ITEM_FOLLOWING); fragmentClassNavIdMap.put(FollowingFragment.class, NavMenuItem.ID_ITEM_FOLLOWING);
fragmentClassNavIdMap.put(EditorsChoiceFragment.class, NavMenuItem.ID_ITEM_EDITORS_CHOICE); fragmentClassNavIdMap.put(EditorsChoiceFragment.class, NavMenuItem.ID_ITEM_EDITORS_CHOICE);
fragmentClassNavIdMap.put(AllContentFragment.class, NavMenuItem.ID_ITEM_ALL_CONTENT); fragmentClassNavIdMap.put(AllContentFragment.class, NavMenuItem.ID_ITEM_ALL_CONTENT);
fragmentClassNavIdMap.put(ShuffleFragment.class, NavMenuItem.ID_ITEM_SHUFFLE);
fragmentClassNavIdMap.put(PublishFragment.class, NavMenuItem.ID_ITEM_NEW_PUBLISH); fragmentClassNavIdMap.put(PublishFragment.class, NavMenuItem.ID_ITEM_NEW_PUBLISH);
fragmentClassNavIdMap.put(ChannelManagerFragment.class, NavMenuItem.ID_ITEM_CHANNELS); fragmentClassNavIdMap.put(ChannelManagerFragment.class, NavMenuItem.ID_ITEM_CHANNELS);
@ -580,7 +586,11 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
public void onClick(View view) { public void onClick(View view) {
if (nowPlayingClaim != null && !Helper.isNullOrEmpty(nowPlayingClaimUrl)) { if (nowPlayingClaim != null && !Helper.isNullOrEmpty(nowPlayingClaimUrl)) {
hideNotifications(); hideNotifications();
openFileUrl(nowPlayingClaimUrl); if (nowPlayingSource == SOURCE_NOW_PLAYING_SHUFFLE) {
openFragment(ShuffleFragment.class, true, NavMenuItem.ID_ITEM_SHUFFLE);
} else {
openFileUrl(nowPlayingClaimUrl);
}
} }
} }
}); });
@ -658,6 +668,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
specialRouteFragmentClassMap.put("settings", SettingsFragment.class); specialRouteFragmentClassMap.put("settings", SettingsFragment.class);
specialRouteFragmentClassMap.put("subscription", FollowingFragment.class); specialRouteFragmentClassMap.put("subscription", FollowingFragment.class);
specialRouteFragmentClassMap.put("subscriptions", FollowingFragment.class); specialRouteFragmentClassMap.put("subscriptions", FollowingFragment.class);
specialRouteFragmentClassMap.put("surf", ShuffleFragment.class);
specialRouteFragmentClassMap.put("wallet", WalletFragment.class); specialRouteFragmentClassMap.put("wallet", WalletFragment.class);
specialRouteFragmentClassMap.put("discover", FollowingFragment.class); specialRouteFragmentClassMap.put("discover", FollowingFragment.class);
} }
@ -813,6 +824,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
case NavMenuItem.ID_ITEM_ALL_CONTENT: case NavMenuItem.ID_ITEM_ALL_CONTENT:
openFragment(AllContentFragment.class, true, NavMenuItem.ID_ITEM_ALL_CONTENT); openFragment(AllContentFragment.class, true, NavMenuItem.ID_ITEM_ALL_CONTENT);
break; break;
case NavMenuItem.ID_ITEM_SHUFFLE:
openFragment(ShuffleFragment.class, true, NavMenuItem.ID_ITEM_SHUFFLE);
break;
case NavMenuItem.ID_ITEM_NEW_PUBLISH: case NavMenuItem.ID_ITEM_NEW_PUBLISH:
openFragment(PublishFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH); openFragment(PublishFragment.class, true, NavMenuItem.ID_ITEM_NEW_PUBLISH);
@ -1027,7 +1041,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
fragment instanceof LibraryFragment || fragment instanceof LibraryFragment ||
fragment instanceof SearchFragment; fragment instanceof SearchFragment;
findViewById(R.id.floating_balance_main_container).setVisibility(!canShowFloatingBalance || inFullscreenMode ? View.INVISIBLE : View.VISIBLE); findViewById(R.id.floating_balance_main_container).setVisibility(!canShowFloatingBalance || inFullscreenMode ? View.INVISIBLE : View.VISIBLE);
if (!(fragment instanceof FileViewFragment) && !inFullscreenMode) { if (!(fragment instanceof FileViewFragment) && !(fragment instanceof ShuffleFragment) && !inFullscreenMode) {
findViewById(R.id.global_now_playing_card).setVisibility(View.VISIBLE); findViewById(R.id.global_now_playing_card).setVisibility(View.VISIBLE);
} }
/*if (!Lbry.SDK_READY && !inFullscreenMode) { /*if (!Lbry.SDK_READY && !inFullscreenMode) {
@ -3025,7 +3039,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
findContentGroup.setItems(Arrays.asList( findContentGroup.setItems(Arrays.asList(
new NavMenuItem(NavMenuItem.ID_ITEM_FOLLOWING, R.string.fa_heart, R.string.following, "Following", context), new NavMenuItem(NavMenuItem.ID_ITEM_FOLLOWING, R.string.fa_heart, R.string.following, "Following", context),
new NavMenuItem(NavMenuItem.ID_ITEM_EDITORS_CHOICE, R.string.fa_star, R.string.editors_choice, "EditorsChoice", context), new NavMenuItem(NavMenuItem.ID_ITEM_EDITORS_CHOICE, R.string.fa_star, R.string.editors_choice, "EditorsChoice", context),
new NavMenuItem(NavMenuItem.ID_ITEM_ALL_CONTENT, R.string.fa_globe_americas, R.string.all_content, "AllContent", context) new NavMenuItem(NavMenuItem.ID_ITEM_ALL_CONTENT, R.string.fa_globe_americas, R.string.all_content, "AllContent", context),
new NavMenuItem(NavMenuItem.ID_ITEM_SHUFFLE, R.string.fa_random, R.string.shuffle, "Shuffle",context)
)); ));
yourContentGroup.setItems(Arrays.asList( yourContentGroup.setItems(Arrays.asList(

View file

@ -20,6 +20,7 @@ public class NavMenuItem {
public static final int ID_ITEM_FOLLOWING = 101; public static final int ID_ITEM_FOLLOWING = 101;
public static final int ID_ITEM_EDITORS_CHOICE = 102; public static final int ID_ITEM_EDITORS_CHOICE = 102;
public static final int ID_ITEM_ALL_CONTENT = 103; public static final int ID_ITEM_ALL_CONTENT = 103;
public static final int ID_ITEM_SHUFFLE = 104;
// Your Content // Your Content
public static final int ID_ITEM_CHANNELS = 201; public static final int ID_ITEM_CHANNELS = 201;

View file

@ -51,8 +51,8 @@ public class CommentListTask extends AsyncTask<Void, Void, List<Comment>> {
options.put("skip_validation", true); options.put("skip_validation", true);
options.put("visible", true); options.put("visible", true);
JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_COMMENT_LIST, options); JSONObject result = (JSONObject) Lbry.genericApiCall(Lbry.METHOD_COMMENT_LIST, options);
android.util.Log.d("LbryMain", result.toString(2));
JSONArray items = result.getJSONArray("items"); JSONArray items = result.getJSONArray("items");
List<Comment> children = new ArrayList<>(); List<Comment> children = new ArrayList<>();
@ -78,6 +78,7 @@ public class CommentListTask extends AsyncTask<Void, Void, List<Comment>> {
} }
} catch (Exception ex) { } catch (Exception ex) {
error = ex; error = ex;
android.util.Log.d("LbryMain", ex.toString(), ex);
} }
return comments; return comments;
} }

View file

@ -227,6 +227,8 @@ public class ChannelContentFragment extends Fragment implements DownloadActionLi
null, null,
getContentSortOrder(), getContentSortOrder(),
contentReleaseTime, contentReleaseTime,
0,
0,
currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage, currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage,
Helper.CONTENT_PAGE_SIZE); Helper.CONTENT_PAGE_SIZE);
} }

View file

@ -414,6 +414,8 @@ public class AllContentFragment extends BaseFragment implements DownloadActionLi
null, null,
getContentSortOrder(), getContentSortOrder(),
contentReleaseTime, contentReleaseTime,
0,
0,
currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage, currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage,
Helper.CONTENT_PAGE_SIZE); Helper.CONTENT_PAGE_SIZE);
} }

View file

@ -9,7 +9,6 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Outline;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -37,7 +36,6 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatSpinner; import androidx.appcompat.widget.AppCompatSpinner;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.core.widget.NestedScrollView; import androidx.core.widget.NestedScrollView;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
@ -167,7 +165,7 @@ public class FileViewFragment extends BaseFragment implements
WalletBalanceListener { WalletBalanceListener {
private static final int RELATED_CONTENT_SIZE = 16; private static final int RELATED_CONTENT_SIZE = 16;
private static final String DEFAULT_PLAYBACK_SPEED = "1x"; private static final String DEFAULT_PLAYBACK_SPEED = "1x";
private static final String CDN_PREFIX = "https://cdn.lbryplayer.xyz"; public static final String CDN_PREFIX = "https://cdn.lbryplayer.xyz";
private PlayerControlView castControlView; private PlayerControlView castControlView;
private Player currentPlayer; private Player currentPlayer;
@ -741,6 +739,13 @@ public class FileViewFragment extends BaseFragment implements
} }
} }
public void onPause() {
if (MainActivity.appPlayer != null) {
MainActivity.nowPlayingSource = MainActivity.SOURCE_NOW_PLAYING_FILE;
}
super.onPause();
}
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
Context context = getContext(); Context context = getContext();
@ -2781,7 +2786,7 @@ public class FileViewFragment extends BaseFragment implements
} }
} }
private static class StreamLoadErrorPolicy extends DefaultLoadErrorHandlingPolicy { public static class StreamLoadErrorPolicy extends DefaultLoadErrorHandlingPolicy {
@Override @Override
public long getRetryDelayMsFor(int dataType, long loadDurationMs, IOException exception, int errorCount) { public long getRetryDelayMsFor(int dataType, long loadDurationMs, IOException exception, int errorCount) {
return exception instanceof ParserException return exception instanceof ParserException

View file

@ -434,6 +434,8 @@ public class FollowingFragment extends BaseFragment implements
null, null,
getContentSortOrder(), getContentSortOrder(),
contentReleaseTime, contentReleaseTime,
0,
0,
currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage, currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage,
Helper.CONTENT_PAGE_SIZE); Helper.CONTENT_PAGE_SIZE);
} }

View file

@ -0,0 +1,748 @@
package io.lbry.browser.ui.findcontent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
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.PlayerView;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import io.lbry.browser.MainActivity;
import io.lbry.browser.R;
import io.lbry.browser.exceptions.LbryUriException;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.lbryinc.Reward;
import io.lbry.browser.tasks.BufferEventTask;
import io.lbry.browser.tasks.GenericTaskHandler;
import io.lbry.browser.tasks.claim.ClaimSearchResultHandler;
import io.lbry.browser.tasks.claim.ClaimSearchTask;
import io.lbry.browser.tasks.lbryinc.ClaimRewardTask;
import io.lbry.browser.tasks.lbryinc.LogFileViewTask;
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.LbryUri;
import io.lbry.browser.utils.Lbryio;
import io.lbry.browser.utils.Predefined;
public class ShuffleFragment extends BaseFragment {
private static final int PAGE_SIZE = 50;
private int currentClaimSearchPage;
private int playlistIndex;
private Claim current;
private List<Claim> playlist;
private long sessionStart;
private ProgressBar surfModeLoading;
private TextView textTitle;
private TextView textPublisher;
private Player player;
private long elapsedDuration = 0;
private long totalDuration = 0;
private boolean elapsedPlaybackScheduled;
private ScheduledExecutorService elapsedPlaybackScheduler;
private boolean playbackStarted;
private long startTimeMillis;
private boolean isPlaying;
private boolean newPlayerCreated;
private String currentUrl;
private Player.EventListener playerListener;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_shuffle, container, false);
surfModeLoading = root.findViewById(R.id.shuffle_loading);
textTitle = root.findViewById(R.id.shuffle_content_title);
textPublisher = root.findViewById(R.id.shuffle_content_publisher);
playerListener = new Player.EventListener() {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == Player.STATE_READY) {
elapsedDuration = MainActivity.appPlayer.getCurrentPosition();
totalDuration = MainActivity.appPlayer.getDuration() < 0 ? 0 : MainActivity.appPlayer.getDuration();
if (!playbackStarted) {
logPlay(currentUrl, startTimeMillis);
playbackStarted = true;
isPlaying = true;
}
renderTotalDuration();
scheduleElapsedPlayback();
hideBuffering();
} else if (playbackState == Player.STATE_BUFFERING) {
Context ctx = getContext();
boolean sendBufferingEvents = true;
if (ctx != null) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(ctx);
sendBufferingEvents = sp.getBoolean(MainActivity.PREFERENCE_KEY_SEND_BUFFERING_EVENTS, true);
}
if (MainActivity.appPlayer != null && MainActivity.appPlayer.getCurrentPosition() > 0 && sendBufferingEvents) {
// we only want to log a buffer event after the media has already started playing
String mediaSourceUrl = getStreamingUrl();
long duration = MainActivity.appPlayer.getDuration();
long position = MainActivity.appPlayer.getCurrentPosition();
// TODO: Determine a hash for the userId
String userIdHash = Helper.SHA256(Lbryio.currentUser != null ? String.valueOf(Lbryio.currentUser.getId()) : "0");
if (mediaSourceUrl.startsWith(FileViewFragment.CDN_PREFIX)) {
BufferEventTask bufferEvent = new BufferEventTask(current.getPermanentUrl(), duration, position, 1, userIdHash);
bufferEvent.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
}
showBuffering();
} else if (playbackState == Player.STATE_ENDED) {
playNextClaim();
} else {
hideBuffering();
}
}
};
Context context = getContext();
PlayerView playerView = root.findViewById(R.id.shuffle_exoplayer_view);
playerView.setOnTouchListener(new SwipeListener(playerView, context) {
@Override
public void onSwipeLeft() { playNextClaim(); }
@Override
public void onSwipeRight() { playPreviousClaim(); }
});
root.findViewById(R.id.shuffle_share_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (current != null) {
try {
String shareUrl = LbryUri.parse(
!Helper.isNullOrEmpty(current.getCanonicalUrl()) ? current.getCanonicalUrl() :
(!Helper.isNullOrEmpty(current.getShortUrl()) ? current.getShortUrl() : current.getPermanentUrl())).toTvString();
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, shareUrl);
MainActivity.startingShareActivity = true;
Intent shareUrlIntent = Intent.createChooser(shareIntent, getString(R.string.share_lbry_content));
shareUrlIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(shareUrlIntent);
} catch (LbryUriException ex) {
// pass
}
}
}
});
root.setOnTouchListener(new SwipeListener(root, context) {
@Override
public void onSwipeLeft() { playNextClaim(); }
@Override
public void onSwipeRight() { playPreviousClaim(); }
});
return root;
}
private String getStreamingUrl() {
return current != null ? String.format("https://cdn.lbryplayer.xyz/content/claims/%s/%s/stream", current.getName(), current.getClaimId()) : "";
}
private Map<String, Object> buildContentOptions() {
Context context = getContext();
boolean canShowMatureContent = false;
if (context != null) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false);
}
return Lbry.buildClaimSearchOptions(
Claim.TYPE_STREAM,
canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS),
null/*contentChannelIds*/,
Arrays.asList(Claim.ORDER_BY_TRENDING_GROUP),
121, // 2 minutes or less
1,
currentClaimSearchPage == 0 ? 1 : currentClaimSearchPage,
PAGE_SIZE);
}
public void onStart() {
super.onStart();
MainActivity activity = (MainActivity) getContext();
if (activity != null) {
activity.hideFloatingWalletBalance();
}
}
public void onResume() {
super.onResume();
sessionStart = System.currentTimeMillis();
MainActivity activity = (MainActivity) getContext();
if (activity != null) {
LbryAnalytics.setCurrentScreen(activity, "Shuffle", "Shuffle");
}
if (MainActivity.appPlayer != null && MainActivity.nowPlayingSource != MainActivity.SOURCE_NOW_PLAYING_SHUFFLE) {
MainActivity.appPlayer.setPlayWhenReady(false);
}
if (playlist == null) {
loadContent();
} else {
if (current != null) {
playbackCurrentClaim();
} else {
startPlaylist();
}
}
}
public void onPause() {
if (MainActivity.appPlayer != null && MainActivity.appPlayer.isPlaying()) {
MainActivity.nowPlayingSource = MainActivity.SOURCE_NOW_PLAYING_SHUFFLE;
}
super.onPause();
}
public void onStop() {
long sessionDuration = System.currentTimeMillis() - sessionStart;
if (sessionStart > 0 && sessionDuration > 0) {
Bundle bundle = new Bundle();
bundle.putLong("duration_ms", sessionDuration);
bundle.putInt("duration", Double.valueOf(Math.ceil(sessionDuration / 1000.0)).intValue());
LbryAnalytics.logEvent(LbryAnalytics.EVENT_SHUFFLE_SESSION, bundle);
}
sessionStart = 0;
MainActivity activity = (MainActivity) getContext();
if (activity != null) {
activity.hideFloatingWalletBalance();
}
super.onStop();
}
private void loadContent() {
if (playlist == null || playlist.size() == 0) {
Helper.setViewVisibility(surfModeLoading, View.VISIBLE);
}
Map<String, Object> claimSearchOptions = buildContentOptions();
ClaimSearchTask task = new ClaimSearchTask(claimSearchOptions, Lbry.LBRY_TV_CONNECTION_STRING, null, new ClaimSearchResultHandler() {
@Override
public void onSuccess(List<Claim> claims, boolean hasReachedEnd) {
if (playlist == null) {
playlist = new ArrayList<>(claims);
startPlaylist();
} else {
for (Claim claim : claims) {
if (!playlist.contains(claim)) {
playlist.add(claim);
}
}
}
Helper.setViewVisibility(surfModeLoading, View.GONE);
}
@Override
public void onError(Exception error) {
Helper.setViewVisibility(surfModeLoading, View.GONE);
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void startPlaylist() {
if (playlist == null || playlist.size() == 0) {
return;
}
playlistIndex = 0;
current = playlist.get(playlistIndex);
checkCurrentClaimIsVideo(false);
playbackCurrentClaim();
}
private void checkCurrentClaimIsVideo(boolean previous) {
while (current == null || current.getMediaType() == null || !current.getMediaType().startsWith("video")) {
// only play videos
if (previous) {
playlistIndex--;
} else {
playlistIndex++;
}
current = playlist.get(playlistIndex);
}
}
private void playPreviousClaim() {
if (playlist == null || playlist.size() == 0) {
return;
}
if (playlistIndex > 0) {
playlistIndex--;
}
current = playlist.get(playlistIndex);
checkCurrentClaimIsVideo(true);
playbackCurrentClaim();
}
private void playNextClaim() {
if (playlist == null || playlist.size() == 0) {
return;
}
if (playlistIndex < playlist.size() - 1) {
playlistIndex++;
}
if (playlist.size() - playlistIndex < 10) {
currentClaimSearchPage++;
loadContent();
}
current = playlist.get(playlistIndex);
checkCurrentClaimIsVideo(false);
playbackCurrentClaim();
}
private void playbackCurrentClaim() {
resetPlayer();
String publisherText = !Helper.isNullOrEmpty(current.getPublisherTitle()) ?
String.format("%s (%s)", current.getPublisherTitle(), current.getPublisherName()) :
!Helper.isNullOrEmpty(current.getPublisherName()) ? current.getPublisherName() : getString(R.string.anonymous);
textTitle.setText(current.getTitle());
textPublisher.setText(publisherText);
Context context = getContext();
if (MainActivity.appPlayer == null && context != null) {
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MOVIE)
.build();
MainActivity.appPlayer = new SimpleExoPlayer.Builder(context).build();
MainActivity.appPlayer.setAudioAttributes(audioAttributes, true);
MainActivity.playerCache =
new SimpleCache(context.getCacheDir(),
new LeastRecentlyUsedCacheEvictor(1024 * 1024 * 256), new ExoDatabaseProvider(context));
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
activity.initMediaSession();
activity.initPlaybackNotification();
}
if (playerListener != null) {
MainActivity.appPlayer.addListener(playerListener);
}
newPlayerCreated = true;
}
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
activity.initPlaybackNotification();
}
View root = getView();
if (root != null) {
PlayerView view = root.findViewById(R.id.shuffle_exoplayer_view);
view.setShutterBackgroundColor(Color.TRANSPARENT);
view.setPlayer(MainActivity.appPlayer);
view.setUseController(true);
if (context instanceof MainActivity) {
((MainActivity) context).getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
if (MainActivity.nowPlayingClaim != null &&
MainActivity.nowPlayingClaim.getClaimId().equalsIgnoreCase(current.getClaimId()) &&
!newPlayerCreated) {
// if the claim is already playing, we don't need to reload the media source
return;
}
if (MainActivity.appPlayer != null) {
showBuffering();
if (context instanceof MainActivity) {
((MainActivity) context).setNowPlayingClaim(current, current.getPermanentUrl());
}
MainActivity.appPlayer.setPlayWhenReady(true);
String userAgent = Util.getUserAgent(context, getString(R.string.app_name));
String mediaSourceUrl = getStreamingUrl();
MediaSource mediaSource = new ProgressiveMediaSource.Factory(
new CacheDataSourceFactory(MainActivity.playerCache, new DefaultDataSourceFactory(context, userAgent)),
new DefaultExtractorsFactory()
).setLoadErrorHandlingPolicy(new FileViewFragment.StreamLoadErrorPolicy()).createMediaSource(Uri.parse(mediaSourceUrl));
MainActivity.appPlayer.prepare(mediaSource, true, true);
}
}
}
private void resetPlayer() {
elapsedDuration = 0;
totalDuration = 0;
renderElapsedDuration();
renderTotalDuration();
elapsedPlaybackScheduled = false;
if (elapsedPlaybackScheduler != null) {
elapsedPlaybackScheduler.shutdownNow();
elapsedPlaybackScheduler = null;
}
playbackStarted = false;
startTimeMillis = 0;
/*
if (MainActivity.appPlayer != null) {
MainActivity.appPlayer.stop(true);
MainActivity.appPlayer.removeListener(fileViewPlayerListener);
PlaybackParameters params = new PlaybackParameters(1.0f);
MainActivity.appPlayer.setPlaybackParameters(params);
}*/
}
private void logPlay(String url, long startTimeMillis) {
long timeToStartMillis = startTimeMillis > 0 ? System.currentTimeMillis() - startTimeMillis : 0;
Bundle bundle = new Bundle();
bundle.putString("uri", url);
bundle.putLong("time_to_start_ms", timeToStartMillis);
bundle.putLong("time_to_start_seconds", Double.valueOf(timeToStartMillis / 1000.0).longValue());
LbryAnalytics.logEvent(LbryAnalytics.EVENT_PLAY, bundle);
logFileView(current.getPermanentUrl(), timeToStartMillis);
}
private void logFileView(String url, long timeToStart) {
if (current != null) {
LogFileViewTask task = new LogFileViewTask(url, current, timeToStart, new GenericTaskHandler() {
@Override
public void beforeStart() { }
@Override
public void onSuccess() {
claimEligibleRewards();
}
@Override
public void onError(Exception error) { }
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private void showBuffering() {
View root = getView();
if (root != null) {
root.findViewById(R.id.player_buffering_progress).setVisibility(View.VISIBLE);
PlayerView playerView = root.findViewById(R.id.shuffle_exoplayer_view);
playerView.findViewById(R.id.player_skip_back_10).setVisibility(View.INVISIBLE);
playerView.findViewById(R.id.player_skip_forward_10).setVisibility(View.INVISIBLE);
}
}
private void hideBuffering() {
View root = getView();
if (root != null) {
root.findViewById(R.id.player_buffering_progress).setVisibility(View.INVISIBLE);
PlayerView playerView = root.findViewById(R.id.shuffle_exoplayer_view);
playerView.findViewById(R.id.player_skip_back_10).setVisibility(View.VISIBLE);
playerView.findViewById(R.id.player_skip_forward_10).setVisibility(View.VISIBLE);
}
}
private void renderElapsedDuration() {
View view = getView();
if (view != null) {
Helper.setViewText(view.findViewById(R.id.player_duration_elapsed), Helper.formatDuration(Double.valueOf(elapsedDuration / 1000.0).longValue()));
}
}
private void renderTotalDuration() {
View view = getView();
if (view != null) {
Helper.setViewText(view.findViewById(R.id.player_duration_total), Helper.formatDuration(Double.valueOf(totalDuration / 1000.0).longValue()));
}
}
private void loadAndScheduleDurations() {
if (MainActivity.appPlayer != null && playbackStarted) {
elapsedDuration = MainActivity.appPlayer.getCurrentPosition() < 0 ? 0 : MainActivity.appPlayer.getCurrentPosition();
totalDuration = MainActivity.appPlayer.getDuration() < 0 ? 0 : MainActivity.appPlayer.getDuration();
renderElapsedDuration();
renderTotalDuration();
scheduleElapsedPlayback();
}
}
private void scheduleElapsedPlayback() {
if (!elapsedPlaybackScheduled) {
elapsedPlaybackScheduler = Executors.newSingleThreadScheduledExecutor();
elapsedPlaybackScheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).runOnUiThread(new Runnable() {
@Override
public void run() {
if (MainActivity.appPlayer != null) {
elapsedDuration = MainActivity.appPlayer.getCurrentPosition();
int elapsedSeconds = Double.valueOf(elapsedDuration / 1000.0).intValue();
renderElapsedDuration();
}
}
});
}
}
}, 0, 500, TimeUnit.MILLISECONDS);
elapsedPlaybackScheduled = true;
}
}
// TODO: Move this call to MainActivity?
private void claimEligibleRewards() {
// attempt to claim eligible rewards after viewing or playing a file (fail silently)
Context context = getContext();
ClaimRewardTask firstStreamTask = new ClaimRewardTask(Reward.TYPE_FIRST_STREAM, null, null, context, eligibleRewardHandler);
ClaimRewardTask dailyViewTask = new ClaimRewardTask(Reward.TYPE_DAILY_VIEW, null, null, context, eligibleRewardHandler);
firstStreamTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
dailyViewTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private ClaimRewardTask.ClaimRewardHandler eligibleRewardHandler = new ClaimRewardTask.ClaimRewardHandler() {
@Override
public void onSuccess(double amountClaimed, String message) {
if (Helper.isNullOrEmpty(message)) {
message = getResources().getQuantityString(
R.plurals.claim_reward_message,
amountClaimed == 1 ? 1 : 2,
new DecimalFormat(Helper.LBC_CURRENCY_FORMAT_PATTERN).format(amountClaimed));
}
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).showMessage(message);
}
}
@Override
public void onError(Exception error) {
// pass
}
};
private static class SwipeListener implements View.OnTouchListener {
private final GestureDetector gestureDetector;
private final View control;
public SwipeListener(View control, Context context) {
this.control = control;
gestureDetector = new GestureDetector(context, new SwipeGestureListener());
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return gestureDetector.onTouchEvent(motionEvent);
}
public void onSwipeLeft() { }
public void onSwipeRight() { }
public void onSwipeTop() { }
public void onSwipeBottom() { }
private final class SwipeGestureListener extends GestureDetector.SimpleOnGestureListener {
private static final int SWIPE_THRESHOLD = 100;
private static final int SWIPE_VELOCITY_THRESHOLD = 100;
@Override
public boolean onDown(MotionEvent e) {
if (control instanceof PlayerView) {
return false;
}
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
boolean result = false;
try {
float diffY = e2.getY() - e1.getY();
float diffX = e2.getX() - e1.getX();
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) {
onSwipeRight();
} else {
onSwipeLeft();
}
result = true;
}
}
else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
if (diffY > 0) {
onSwipeBottom();
} else {
onSwipeTop();
}
result = true;
}
} catch (Exception ex) {
// pass
}
return result;
}
}
}
@Override
public boolean shouldHideGlobalPlayer() {
return true;
}
private static final List<String> contentChannelIds = Arrays.asList(
"3fec094c5937e9eb4e8f5e71e4ca430e8a993d03",
"6184648aab0431c4c95c649072d1f9ff08b9bb7c",
"b5d31cde873073718c033076656a27471e392afc",
"7317cdf6f62be93b22295062e191f6ba59a5db26",
"1cdb5d0bdcb484907d0a2fea4efdfe0153838642",
"b516294f541a18ce00b71a60b2c82ad2f87ff78d",
"91e42cc450075f2c4c245bac7617bf903f16b4ce",
"b6e207c5f8c58e7c8362cd05a1501bf2f5b694f2",
"25f384bd95e218f6ac37fcaca99ed40f36760d8c",
"f33657a2fcbab2dc3ce555d5d6728f8758af7bc7",
"294f5c164da5ac9735658b2d58d8fee6745dfc45",
"119a2e8c0b50f78d3861636d37c3b44ba8e689b5",
"7b23cca3f49059f005e812be03931c81272eaac4",
"fb0efeaa3788d1292bb49a94d77622503fe08129",
"797a528c49b6535560f7fd8222b121b0223287c8",
"bc490776f367b8afccf0ea7349d657431ba1ded6",
"48c7ea8bc2c4adba09bf21a29689e3b8c2967522",
"bf7490f905904e79de5c90e472bb9e6f26e634a0",
"df961194a798cc76306b9290701130c592530fb6",
"cf0be9078d76951e2e228df68b5b0bbf71313aaa",
"d746ac8d782f94d12d176c7a591f5bf8365bef3d",
"1f30267438257020f08abf452746a48e53a71ad5",
"4ad942982e43326c7700b1b6443049b3cfd82161",
"1cdb5d0bdcb484907d0a2fea4efdfe0153838642",
"6616707e1109aaa1c11b9f399f914d0cfb4f5303",
"4ee7cfaf1fc50a6df858ed0b99c278d633bccca9",
"b7d02b4a0036114732c072269adb891dc5e34ca4",
"9c51c1a119137cd17ed5ae09daa80c1cab6ac01d",
"5f2a5c14b971a6f5eed0a67dc7af3a3fe5c0b6a4",
"0e2b5b4cf59e859860000ff123dc12a317ad416b",
"3fe68ad3da93065e35c37b14fbeef88b4b7785ed",
"fd7ffcbafb74412a8812df4720feaf11fe70fe12",
"b4c30fe36b79870a79c55e1e909adb5ad23f323f",
"92c0f2f3239f1f61496997bd2cdc197ec51bd423",
"29193e9240a71a735639c66ee954e68414f11236",
"25f384bd95e218f6ac37fcaca99ed40f36760d8c",
"87b13b074936b1f42a7c6758c7c2995f58c602e7",
"8d935c6c30510e1dfc10f803a9646fa8aa128b07",
"8f4fecfc836ea33798ee3e5cef56926fa54e2cf9",
"9a5dfcb1a4b29c3a1598392d039744b9938b5a26",
"c5724e280283cd985186af9a62494aae377daabd",
"b39833be3032bbe1005f4f719f379a4621faeb13",
"589276465a23c589801d874f484cc39f307d7ec7",
"fb364ef587872515f545a5b4b3182b58073f230f",
"6c0bf1fed2705d675da950a94e4af004ec975a06",
"b924ac36b7499591f7929d9c4903de79b07b1cb9",
"113515e893b8186595595e594ecc410bae50c026",
"72f9815b087b6d346745e3de71a6ce5fe73a8677",
"b0198a465290f065378f3535666bee0653d6a9bb",
"020ebeb40642bfb4bc3d9f6d28c098afc0a47481",
"5c15e604c4207f52c8cf58fe21e63164c230e257",
"273a2fa759f1a9f56b078633ea2f08fc2406002a",
"930fc43ca7bae20d4706543e97175d1872b0671f",
"0cb2ec46f06ba85520a1c1a56706acf35d5176dd",
"057053dfb657aaa98553e2c544b06e1a2371557e",
"64e091964a611a48424d254a3de2b952d0d6565a",
"50ebba2b06908f93d7963b1c6826cc0fd6104477",
"374ff82251a384601da73f30485c3ac8d7f4176b",
"1487afc813124abbeb0629d2172be0f01ccec3bf",
"6a4fa1a68b92336e64006a4310cb160b07854329",
"15f986a262fc6eff5774050c94d174c0533d505d",
"6184648aab0431c4c95c649072d1f9ff08b9bb7c",
"1efa9b640ad980b2ec53834d60e9cff9554979cd",
"064d4999ea15e433e06f16f391922390acab01cb",
"4884e30b93b3c4c123a83154516196095f9e831e",
"2827bfc459c12d7c6d280cbacee750811291d4ba",
"9626816275585ac3443e7cddd1272c8652c23f1d",
"a2e1bb1fed32c6a6290b679785dd31ca5c59cb5f",
"d9535951222dd7a1ff7f763872cb0df44f7962bf",
"243b6f18093ff97c861d0568c7d3379606201a4b",
"1ce5ac7bab7f2e82af02305ced3c095e320b52e5",
"3e63119a8503a6f666b0a736c8fdeb9e79d11eb4",
"e33372c0d8b2cdd3e12252962ee1671d66143075",
"7364ba2ac9090e468855ce9074bb50c306196f4c",
"d6350f9158825662b99e4b5e0442bcc94d39bc11",
"2a294ea41312c6da8de5ebd0f18dbfb2963bb1a4",
"44c49bbab8a3e9999f3ba9dee0186288b1d960a7",
"2305db36455aa0d18571015b9e9bd0950262aa0f",
"82f1d8c257d3e76b711a5cecd1e49bd3fa6a9de9",
"faed2b028a9b5a712d5180eaa6fd2aa619f941bc",
"39ac239b5687f7d1c2ba74cd020b3547545dfdaf",
"5737eb22f6119f27c9afccfe73ba710afd885371",
"5f22b6daf7204d73cf79d3ff0b46fc4fe237c7f7",
"3583f5a570af6870504eea5a5f7afad6e1508508",
"b0e489f986c345aef23c4a48d91cbcf5a6fdb9ac",
"ba79c80788a9e1751e49ad401f5692d86f73a2db",
"c54323436f50c633c870298bb374ac8e7560e6cd",
"83725c7ee23bd4a8ca28a4fab0e313409def1dc7",
"61c4e8636704f2f38bbe88b1f30ef0d74d6c0f49",
"87ef9ba36019f7f3bf217cf47511645893b13f2e",
"1bd0adfcf1c75bafc1ba3fc9b65a1a0470df6a91",
"1be15348c51955179b7bf9aa90230a9425927ef6",
"1f45ab3495df2c3be7d9c882bca9966305115cbb",
"7317777e3751efa66218f6da5ef0d01dda69af48",
"3346a4ff80b70ee7eea8a79fc79f19c43bb4464a",
"452916a904030d2ce4ea2440fad2d0774e7296d9",
"2675ef3adf52ebf8a44ff5da4306c293dfa6f901",
"e2a76643735f8611a561794509c6bb2aac70eb04",
"dc577db3caf5ff83a3b573ba92f2d447f067eee1",
"89c4cf244099918b1d3ed413df27d4216e97b499",
"4b2b5822c8af3074c6ef9b789a8142d0ef623402",
"3c5794f775975669745c412b0c30f48991d9e455",
"1df464dbb302ced815c61431a5548a273e6de8e1"
);
}

View file

@ -375,6 +375,23 @@ public final class Lbry {
"any_tags", "channel_ids", "order_by", "not_tags", "not_channel_ids", "urls" "any_tags", "channel_ids", "order_by", "not_tags", "not_channel_ids", "urls"
}; };
// build claim search for surf mode
public static Map<String, Object> buildClaimSearchOptions(
String claimType, List<String> notTags, List<String> channelIds, List<String> orderBy, long maxDuration, int limitClaimsPerChannel, int page, int pageSize) {
return buildClaimSearchOptions(
Collections.singletonList(claimType),
null,
notTags,
channelIds,
null,
orderBy,
null,
maxDuration,
limitClaimsPerChannel,
page,
pageSize);
}
public static Map<String, Object> buildClaimSearchOptions( public static Map<String, Object> buildClaimSearchOptions(
String claimType, String claimType,
List<String> anyTags, List<String> anyTags,
@ -393,6 +410,8 @@ public final class Lbry {
notChannelIds, notChannelIds,
orderBy, orderBy,
releaseTime, releaseTime,
0,
0,
page, page,
pageSize); pageSize);
} }
@ -405,6 +424,8 @@ public final class Lbry {
List<String> notChannelIds, List<String> notChannelIds,
List<String> orderBy, List<String> orderBy,
String releaseTime, String releaseTime,
long maxDuration,
int limitClaimsPerChannel,
int page, int page,
int pageSize) { int pageSize) {
Map<String, Object> options = new HashMap<>(); Map<String, Object> options = new HashMap<>();
@ -417,6 +438,12 @@ public final class Lbry {
if (!Helper.isNullOrEmpty(releaseTime)) { if (!Helper.isNullOrEmpty(releaseTime)) {
options.put("release_time", releaseTime); options.put("release_time", releaseTime);
} }
if (maxDuration > 0) {
options.put("duration", String.format("<%d", maxDuration));
}
if (limitClaimsPerChannel > 0) {
options.put("limit_claims_per_channel", limitClaimsPerChannel);
}
addClaimSearchListOption("any_tags", anyTags, options); addClaimSearchListOption("any_tags", anyTags, options);
addClaimSearchListOption("not_tags", notTags, options); addClaimSearchListOption("not_tags", notTags, options);

View file

@ -28,6 +28,7 @@ public class LbryAnalytics {
public static final String EVENT_PUBLISH_UPDATE = "publish_update"; public static final String EVENT_PUBLISH_UPDATE = "publish_update";
public static final String EVENT_CHANNEL_CREATE = "channel_create"; public static final String EVENT_CHANNEL_CREATE = "channel_create";
public static final String EVENT_SEARCH = "search"; public static final String EVENT_SEARCH = "search";
public static final String EVENT_SHUFFLE_SESSION = "shuffle_session";
private static FirebaseAnalytics analytics; private static FirebaseAnalytics analytics;

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:background="@android:color/black"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/shuffle_exoplayer_container"
android:background="@android:color/black"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/shuffle_content_details">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/shuffle_exoplayer_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:controller_layout_id="@layout/exo_playback_control_view"/>
<ProgressBar
android:id="@+id/player_buffering_progress"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_centerInParent="true"
android:visibility="gone" />
</RelativeLayout>
<LinearLayout
android:id="@+id/shuffle_content_details"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:layout_alignParentBottom="true">
<TextView
android:id="@+id/shuffle_content_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:textColor="@color/white"
android:textFontWeight="300"
android:minLines="1"
android:maxLines="3"
android:textSize="20sp"
/>
<TextView
android:id="@+id/shuffle_content_publisher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:textColor="@color/white"
android:textFontWeight="300"
android:minLines="1"
android:maxLines="1"
android:textSize="14sp" />
</LinearLayout>
<ProgressBar
android:id="@+id/shuffle_loading"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerInParent="true"
android:visibility="gone" />
<RelativeLayout
android:id="@+id/shuffle_share_button"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:clickable="true"
android:background="?attr/selectableItemBackground">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_centerInParent="true"
android:tint="@color/white"
android:src="@drawable/ic_share" />
</RelativeLayout>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.slideshow.SlideshowFragment">
<TextView
android:id="@+id/text_slideshow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -20,6 +20,7 @@
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="about">About</string> <string name="about">About</string>
<string name="sign_in">Sign In</string> <string name="sign_in">Sign In</string>
<string name="shuffle">Shuffle</string>
<string name="startup_failed">App startup failed. Please check your data connection and try again. If this problem persists, please email hello@lbry.com</string> <string name="startup_failed">App startup failed. Please check your data connection and try again. If this problem persists, please email hello@lbry.com</string>
<string name="no_claim_search_content">No content to display at this time. Please refine your selection or check back later.</string> <string name="no_claim_search_content">No content to display at this time. Please refine your selection or check back later.</string>
@ -650,4 +651,6 @@
<string name="fa_asterisk" translatable="false">&#xf069;</string> <string name="fa_asterisk" translatable="false">&#xf069;</string>
<string name="fa_comment_alt" translatable="false">&#xf27a;</string> <string name="fa_comment_alt" translatable="false">&#xf27a;</string>
<string name="fa_broadcast_tower" translatable="false">&#xf519;</string>
<string name="fa_random" translatable="false">&#xf074;</string>
</resources> </resources>