Library page. URL and view history.

This commit is contained in:
Akinwale Ariwodola 2020-05-15 20:07:05 +01:00
parent 47f56950a5
commit 244d54ed39
25 changed files with 1711 additions and 51 deletions

View file

@ -169,9 +169,9 @@ public class FileViewActivity extends AppCompatActivity {
ClaimCacheKey key = new ClaimCacheKey();
key.setClaimId(claimId);
key.setUrl(url); // use the same url for the key so that we can match the key for any value that's the same
if (Lbry.claimCache.containsKey(key)) {
claim = Lbry.claimCache.get(key);
Helper.saveViewHistory(url, claim);
checkAndResetNowPlayingClaim();
if (claim.getFile() == null) {
loadFile();
@ -314,6 +314,7 @@ public class FileViewActivity extends AppCompatActivity {
if (Lbry.claimCache.containsKey(key)) {
claim = Lbry.claimCache.get(key);
Helper.saveUrlHistory(url, claim.getTitle(), UrlSuggestion.TYPE_FILE);
Helper.saveViewHistory(url, claim);
checkAndResetNowPlayingClaim();
if (claim.getFile() == null) {
loadFile();
@ -423,7 +424,7 @@ public class FileViewActivity extends AppCompatActivity {
String claimId = claim.getClaimId();
FileListTask task = new FileListTask(claimId, null, new FileListTask.FileListResultHandler() {
@Override
public void onSuccess(List<LbryFile> files) {
public void onSuccess(List<LbryFile> files, boolean hasReachedEnd) {
if (files.size() > 0) {
claim.setFile(files.get(0));
checkIsFileComplete();
@ -445,7 +446,7 @@ public class FileViewActivity extends AppCompatActivity {
if (initialFileLoadDone) {
restoreMainActionButton();
}
if (claim != null && claim.isFree()) {
if (claim != null && claim.isFree() && (claim.isPlayable() || claim.isViewable())) {
onMainActionButtonClicked();
}
}
@ -491,6 +492,8 @@ public class FileViewActivity extends AppCompatActivity {
Helper.saveUrlHistory(url, claim.getTitle(), UrlSuggestion.TYPE_FILE);
// also save view history
Helper.saveViewHistory(url, claim);
checkAndResetNowPlayingClaim();
loadFile();
renderClaim();
@ -1576,9 +1579,11 @@ public class FileViewActivity extends AppCompatActivity {
String uri = intent.getStringExtra("uri");
String outpoint = intent.getStringExtra("outpoint");
String fileInfoJson = intent.getStringExtra("file_info");
double progress = intent.getDoubleExtra("progress", 0);
if (claim == null || uri == null || outpoint == null || (fileInfoJson == null && !"abort".equals(downloadAction))) {
return;
}
onRelatedDownloadAction(downloadAction, uri, outpoint, fileInfoJson, progress);
if (claim != null && !claim.getPermanentUrl().equalsIgnoreCase(uri)) {
return;
}
@ -1604,7 +1609,6 @@ public class FileViewActivity extends AppCompatActivity {
} 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);
@ -1692,6 +1696,26 @@ public class FileViewActivity extends AppCompatActivity {
}
}
private void onRelatedDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) {
if ("abort".equals(downloadAction)) {
if (relatedContentAdapter != null) {
relatedContentAdapter.clearFileForClaimOrUrl(outpoint, uri);
}
return;
}
try {
JSONObject fileInfo = new JSONObject(fileInfoJson);
LbryFile claimFile = LbryFile.fromJSONObject(fileInfo);
String claimId = claimFile.getClaimId();
if (relatedContentAdapter != null) {
relatedContentAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri);
}
} catch (JSONException ex) {
// invalid file info for download
}
}
private void bringMainTaskToFront() {
if (backStackLost) {
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

View file

@ -15,6 +15,7 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
@ -32,11 +33,13 @@ import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ext.cast.CastPlayer;
import com.google.android.exoplayer2.offline.Download;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.tasks.OnCompleteListener;
@ -78,6 +81,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
@ -89,6 +93,7 @@ import io.lbry.browser.adapter.UrlSuggestionListAdapter;
import io.lbry.browser.data.DatabaseHelper;
import io.lbry.browser.dialog.ContentScopeDialogFragment;
import io.lbry.browser.exceptions.LbryUriException;
import io.lbry.browser.listener.DownloadActionListener;
import io.lbry.browser.listener.FetchChannelsListener;
import io.lbry.browser.listener.SdkStatusListener;
import io.lbry.browser.listener.WalletBalanceListener;
@ -121,6 +126,7 @@ 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.library.LibraryFragment;
import io.lbry.browser.ui.other.AboutFragment;
import io.lbry.browser.ui.search.SearchFragment;
import io.lbry.browser.ui.other.SettingsFragment;
@ -133,6 +139,7 @@ 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;
import io.lbry.lbrysdk.ServiceHelper;
import io.lbry.lbrysdk.Utils;
@ -246,6 +253,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
@Getter
private DatabaseHelper dbHelper;
private int selectedMenuItemId = -1;
private List<DownloadActionListener> downloadActionListeners;
private List<SdkStatusListener> sdkStatusListeners;
private List<WalletBalanceListener> walletBalanceListeners;
private List<FetchChannelsListener> fetchChannelsListeners;
@ -278,6 +286,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
// your content
NavMenuItem.ID_ITEM_CHANNELS,
NavMenuItem.ID_ITEM_LIBRARY,
// wallet
NavMenuItem.ID_ITEM_WALLET,
@ -336,6 +345,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
// other
openNavFragments = new HashMap<>();
downloadActionListeners = new ArrayList<>();
sdkStatusListeners = new ArrayList<>();
walletBalanceListeners = new ArrayList<>();
fetchChannelsListeners = new ArrayList<>();
@ -355,6 +365,14 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
shouldOpenUserSelectedMenuItem = false;
}
}
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
if (slideOffset != 0) {
clearWunderbarFocus(findViewById(R.id.wunderbar));
}
super.onDrawerSlide(drawerView, slideOffset);
}
};
drawer.addDrawerListener(toggle);
toggle.syncState();
@ -435,7 +453,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
specialRouteFragmentClassMap.put("channels", ChannelManagerFragment.class);
specialRouteFragmentClassMap.put("invite", InvitesFragment.class);
specialRouteFragmentClassMap.put("invites", InvitesFragment.class);
//specialRouteFragmentClassMap.put("library", LibraryFragment.class);
specialRouteFragmentClassMap.put("library", LibraryFragment.class);
//specialRouteFragmentClassMap.put("publish", PublishFragment.class);
//specialRouteFragmentClassMap.put("publishes", PublishesFragment.class);
specialRouteFragmentClassMap.put("following", FollowingFragment.class);
@ -452,6 +470,16 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
checkNotificationOpenIntent(intent);
}
public void addDownloadActionListener(DownloadActionListener listener) {
if (!downloadActionListeners.contains(listener)) {
downloadActionListeners.add(listener);
}
}
public void removeDownloadActionListener(DownloadActionListener listener) {
downloadActionListeners.remove(listener);
}
public void addSdkStatusListener(SdkStatusListener listener) {
if (!sdkStatusListeners.contains(listener)) {
sdkStatusListeners.add(listener);
@ -504,6 +532,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
case NavMenuItem.ID_ITEM_CHANNELS:
openFragment(ChannelManagerFragment.class, true, NavMenuItem.ID_ITEM_CHANNELS);
break;
case NavMenuItem.ID_ITEM_LIBRARY:
openFragment(LibraryFragment.class, true, NavMenuItem.ID_ITEM_LIBRARY);
break;
case NavMenuItem.ID_ITEM_WALLET:
openFragment(WalletFragment.class, true, NavMenuItem.ID_ITEM_WALLET);
@ -1094,6 +1125,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
} else if (!appStarted) {
// first run completed, startup
startup();
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
openFragment(FollowingFragment.class, false, NavMenuItem.ID_ITEM_FOLLOWING);
}
}
@ -1146,9 +1179,12 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}
findViewById(R.id.global_sdk_initializing_status).setVisibility(View.GONE);
syncWalletAndLoadPreferences();
scheduleWalletBalanceUpdate();
scheduleWalletSyncTask();
fetchChannels();
initFloatingWalletBalance();
}
@ -1317,7 +1353,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
return walletSyncEnabled && Lbryio.isSignedIn();
}
private void syncWalletAndLoadPreferences() {
public void syncWalletAndLoadPreferences() {
if (!userSyncEnabled()) {
return;
}
@ -1629,6 +1665,26 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
actionBar.show();
}
}
private void renderStartupFailed(Map<Integer, Boolean> startupStages) {
Map<Integer, Integer> startupStageIconIds = new HashMap<>();
startupStageIconIds.put(STARTUP_STAGE_INSTALL_ID_LOADED, R.id.startup_stage_icon_install_id);
startupStageIconIds.put(STARTUP_STAGE_KNOWN_TAGS_LOADED, R.id.startup_stage_icon_known_tags);
startupStageIconIds.put(STARTUP_STAGE_EXCHANGE_RATE_LOADED, R.id.startup_stage_icon_exchange_rate);
startupStageIconIds.put(STARTUP_STAGE_USER_AUTHENTICATED, R.id.startup_stage_icon_user_authenticated);
startupStageIconIds.put(STARTUP_STAGE_NEW_INSTALL_DONE, R.id.startup_stage_icon_install_new);
startupStageIconIds.put(STARTUP_STAGE_SUBSCRIPTIONS_LOADED, R.id.startup_stage_icon_subscriptions_loaded);
startupStageIconIds.put(STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED, R.id.startup_stage_icon_subscriptions_resolved);
for (Integer key : startupStages.keySet()) {
boolean stageDone = startupStages.get(key);
ImageView icon = findViewById(startupStageIconIds.get(key));
icon.setImageResource(stageDone ? R.drawable.ic_check : R.drawable.ic_close);
icon.setColorFilter(stageDone ? Color.WHITE : Color.RED);
}
findViewById(R.id.splash_view_loading_container).setVisibility(View.GONE);
findViewById(R.id.splash_view_error_container).setVisibility(View.VISIBLE);
}
private void startup() {
final Context context = this;
@ -1636,11 +1692,23 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
// perform some tasks before launching
(new AsyncTask<Void, Void, Boolean>() {
private Map<Integer, Boolean> startupStages = new HashMap<>();
private void initStartupStages() {
startupStages.put(STARTUP_STAGE_INSTALL_ID_LOADED, false);
startupStages.put(STARTUP_STAGE_KNOWN_TAGS_LOADED, false);
startupStages.put(STARTUP_STAGE_EXCHANGE_RATE_LOADED, false);
startupStages.put(STARTUP_STAGE_USER_AUTHENTICATED, false);
startupStages.put(STARTUP_STAGE_NEW_INSTALL_DONE, false);
startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_LOADED, false);
startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED, false);
}
protected void onPreExecute() {
hideActionBar();
lockDrawer();
findViewById(R.id.splash_view).setVisibility(View.VISIBLE);
LbryAnalytics.setCurrentScreen(MainActivity.this, "Splash", "Splash");
initStartupStages();
}
protected Boolean doInBackground(Void... params) {
BufferedReader reader = null;
@ -1652,29 +1720,35 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
String installId = reader.readLine();
if (Helper.isNullOrEmpty(installId)) {
// no install_id found (first run didn't start the sdk successfully?)
startupStages.put(STARTUP_STAGE_INSTALL_ID_LOADED, false);
return false;
}
Lbry.INSTALLATION_ID = installId;
startupStages.put(STARTUP_STAGE_INSTALL_ID_LOADED, true);
SQLiteDatabase db = dbHelper.getReadableDatabase();
List<Tag> fetchedTags = DatabaseHelper.getTags(db);
Lbry.knownTags = Helper.mergeKnownTags(fetchedTags);
Collections.sort(Lbry.knownTags, new Tag());
Lbry.followedTags = Helper.filterFollowedTags(Lbry.knownTags);
startupStages.put(STARTUP_STAGE_KNOWN_TAGS_LOADED, true);
// load the exchange rate
Lbryio.loadExchangeRate();
if (Lbryio.LBCUSDRate == 0) {
Lbryio.loadExchangeRate();
return false;
}
startupStages.put(STARTUP_STAGE_EXCHANGE_RATE_LOADED, true);
Lbry.INSTALLATION_ID = installId;
if (Lbryio.currentUser == null) {
Lbryio.authenticate(context);
}
Lbryio.authenticate(context);
if (Lbryio.currentUser == null) {
throw new Exception("Did not retrieve authenticated user.");
}
startupStages.put(STARTUP_STAGE_USER_AUTHENTICATED, true);
Lbryio.newInstall(context);
startupStages.put(STARTUP_STAGE_NEW_INSTALL_DONE, true);
// (light) fetch subscriptions
if (Lbryio.subscriptions.size() == 0) {
@ -1701,6 +1775,13 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
Lbryio.cacheResolvedSubscriptions = resolvedSubs;
}
}
// if no exceptions occurred here, subscriptions have been loaded and resolved
startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_LOADED, true);
startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED, true);
} else {
startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_LOADED, true);
startupStages.put(STARTUP_STAGE_SUBSCRIPTIONS_RESOLVED, true);
}
} catch (Exception ex) {
// nope
@ -1714,8 +1795,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
}
protected void onPostExecute(Boolean startupSuccessful) {
if (!startupSuccessful) {
Toast.makeText(context, R.string.startup_failed, Toast.LENGTH_LONG).show();
finish();
// show which startup stage failed
renderStartupFailed(startupStages);
return;
}
@ -1901,6 +1982,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
private void registerServiceActionsReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_EVENT);
intentFilter.addAction(LbrynetService.LBRY_SDK_SERVICE_STARTED);
intentFilter.addAction(LbrynetService.ACTION_STOP_SERVICE);
serviceActionsReceiver = new BroadcastReceiver() {
@ -1920,6 +2002,19 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener
notificationManager.notify(1, svcNotification);
}
}, 1000);
} else if (DownloadManager.ACTION_DOWNLOAD_EVENT.equalsIgnoreCase(action)) {
String downloadAction = intent.getStringExtra("action");
String uri = intent.getStringExtra("uri");
String outpoint = intent.getStringExtra("outpoint");
String fileInfoJson = intent.getStringExtra("file_info");
double progress = intent.getDoubleExtra("progress", 0);
if (uri == null || outpoint == null || (fileInfoJson == null && !"abort".equals(downloadAction))) {
return;
}
for (DownloadActionListener listener : downloadActionListeners) {
listener.onDownloadAction(downloadAction, uri, outpoint, fileInfoJson, progress);
}
}
}
};
@ -1968,8 +2063,8 @@ 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_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)
));

View file

@ -6,6 +6,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
@ -17,11 +18,14 @@ import com.google.android.material.snackbar.Snackbar;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.lbry.browser.R;
import io.lbry.browser.listener.SelectionModeListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.LbryFile;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.LbryUri;
import io.lbry.browser.utils.Lbryio;
@ -33,6 +37,11 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
private static final int VIEW_TYPE_CHANNEL = 2;
private static final int VIEW_TYPE_FEATURED = 3; // featured search result
private Map<String, Claim> quickClaimIdMap;
private Map<String, Claim> quickClaimUrlMap;
private Map<String, Boolean> notFoundClaimIdMap;
private Map<String, Boolean> notFoundClaimUrlMap;
@Setter
private boolean canEnterSelectionMode;
private Context context;
@ -50,6 +59,10 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
this.context = context;
this.items = new ArrayList<>(items);
this.selectedItems = new ArrayList<>();
quickClaimIdMap = new HashMap<>();
quickClaimUrlMap = new HashMap<>();
notFoundClaimIdMap = new HashMap<>();
notFoundClaimUrlMap = new HashMap<>();
}
public List<Claim> getSelectedItems() {
@ -77,6 +90,10 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
public void clearItems() {
clearSelectedItems();
this.items.clear();
quickClaimIdMap.clear();
quickClaimUrlMap.clear();
notFoundClaimIdMap.clear();
notFoundClaimUrlMap.clear();
notifyDataSetChanged();
}
@ -91,6 +108,9 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
items.add(claim);
}
}
notFoundClaimUrlMap.clear();
notFoundClaimIdMap.clear();
notifyDataSetChanged();
}
public void setItems(List<Claim> claims) {
@ -119,6 +139,9 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
protected View repostInfoView;
protected TextView repostChannelView;
protected View selectedOverlayView;
protected TextView fileSizeView;
protected ProgressBar downloadProgressView;
protected TextView deviceView;
public ViewHolder(View v) {
super(v);
feeContainer = v.findViewById(R.id.claim_fee_container);
@ -135,6 +158,9 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
repostInfoView = v.findViewById(R.id.claim_repost_info);
repostChannelView = v.findViewById(R.id.claim_repost_channel);
selectedOverlayView = v.findViewById(R.id.claim_selected_overlay);
fileSizeView = v.findViewById(R.id.claim_file_size);
downloadProgressView = v.findViewById(R.id.claim_download_progress);
deviceView = v.findViewById(R.id.claim_view_device);
}
}
@ -156,6 +182,68 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
return Claim.TYPE_CHANNEL.equalsIgnoreCase(actualClaim.getValueType()) ? VIEW_TYPE_CHANNEL : VIEW_TYPE_STREAM;
}
public void updateFileForClaimByIdOrUrl(LbryFile file, String claimId, String url) {
updateFileForClaimByIdOrUrl(file, claimId, url, false);
}
public void updateFileForClaimByIdOrUrl(LbryFile file, String claimId, String url, boolean skipNotFound) {
if (!skipNotFound) {
if (notFoundClaimIdMap.containsKey(claimId) && notFoundClaimUrlMap.containsKey(url)) {
return;
}
}
if (quickClaimIdMap.containsKey(claimId)) {
quickClaimIdMap.get(claimId).setFile(file);
notifyDataSetChanged();
return;
}
if (quickClaimUrlMap.containsKey(claimId)) {
quickClaimUrlMap.get(claimId).setFile(file);
notifyDataSetChanged();
return;
}
boolean claimFound = false;
for (int i = 0; i < items.size(); i++) {
Claim claim = items.get(i);
if (claimId.equalsIgnoreCase(claim.getClaimId()) || url.equalsIgnoreCase(claim.getPermanentUrl())) {
quickClaimIdMap.put(claimId, claim);
quickClaimUrlMap.put(url, claim);
claim.setFile(file);
notifyDataSetChanged();
claimFound = true;
break;
}
}
if (!claimFound) {
notFoundClaimIdMap.put(claimId, true);
notFoundClaimUrlMap.put(url, true);
}
}
public void clearFileForClaimOrUrl(String outpoint, String url) {
clearFileForClaimOrUrl(outpoint, url, false);
notifyDataSetChanged();
}
public void clearFileForClaimOrUrl(String outpoint, String url, boolean remove) {
int claimIndex = -1;
for (int i = 0; i < items.size(); i++) {
Claim claim = items.get(i);
if (outpoint.equalsIgnoreCase(claim.getOutpoint()) || url.equalsIgnoreCase(claim.getPermanentUrl())) {
claimIndex = i;
claim.setFile(null);
break;
}
}
if (remove && claimIndex > -1) {
Claim removed = items.remove(claimIndex);
selectedItems.remove(removed);
}
notifyDataSetChanged();
}
@Override
public ClaimListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
int viewResourceId = -1;
@ -290,6 +378,24 @@ public class ClaimListAdapter extends RecyclerView.Adapter<ClaimListAdapter.View
publishTime, System.currentTimeMillis(), 0, DateUtils.FORMAT_ABBREV_RELATIVE));
vh.durationView.setVisibility(duration > 0 ? View.VISIBLE : View.GONE);
vh.durationView.setText(Helper.formatDuration(duration));
LbryFile claimFile = item.getFile();
boolean isDownloading = false;
int progress = 0;
String fileSizeString = claimFile == null ? null : Helper.formatBytes(claimFile.getTotalBytes(), false);
if (claimFile != null && !claimFile.isCompleted() && claimFile.getWrittenBytes() < claimFile.getTotalBytes()) {
isDownloading = true;
progress = claimFile.getTotalBytes() > 0 ?
Double.valueOf(((double) claimFile.getWrittenBytes() / (double) claimFile.getTotalBytes()) * 100.0).intValue() : 0;
fileSizeString = String.format("%s / %s",
Helper.formatBytes(claimFile.getWrittenBytes(), false),
Helper.formatBytes(claimFile.getTotalBytes(), false));
}
Helper.setViewText(vh.fileSizeView, fileSizeString);
Helper.setViewVisibility(vh.downloadProgressView, isDownloading ? View.VISIBLE : View.INVISIBLE);
Helper.setViewProgress(vh.downloadProgressView, progress);
Helper.setViewText(vh.deviceView, item.getDevice());
} else if (Claim.TYPE_CHANNEL.equalsIgnoreCase(item.getValueType())) {
if (!Helper.isNullOrEmpty(thumbnailUrl)) {
Glide.with(context.getApplicationContext()).

View file

@ -4,7 +4,10 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.opengl.Visibility;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@ -66,6 +69,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
private static final String SQL_INSERT_VIEW_HISTORY =
"REPLACE INTO view_history (url, claim_id, claim_name, cost, title, publisher_claim_id, publisher_name, publisher_title, thumbnail_url, device, release_time, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final String SQL_GET_VIEW_HISTORY =
"SELECT url, claim_id, claim_name, cost, title, publisher_claim_id, publisher_name, publisher_title, thumbnail_url, device, release_time, timestamp " +
"FROM view_history WHERE '' = ? OR timestamp < ? ORDER BY timestamp DESC LIMIT %d";
private static final String SQL_CLEAR_VIEW_HISTORY = "DELETE FROM view_history";
private static final String SQL_CLEAR_VIEW_HISTORY_BY_DEVICE = "DELETE FROM view_history WHERE device = ?";
private static final String SQL_CLEAR_VIEW_HISTORY_BEFORE_TIME = "DELETE FROM view_history WHERE timestamp < ?";
@ -147,6 +153,41 @@ public class DatabaseHelper extends SQLiteOpenHelper {
});
}
public static List<ViewHistory> getViewHistory(String lastTimestamp, int pageLimit, SQLiteDatabase db) {
List<ViewHistory> history = new ArrayList<>();
Cursor cursor = null;
try {
String arg = lastTimestamp == null ? "" : lastTimestamp;
cursor = db.rawQuery(String.format(SQL_GET_VIEW_HISTORY, pageLimit), new String[] { arg, arg });
while (cursor.moveToNext()) {
ViewHistory item = new ViewHistory();
int cursorIndex = 0;
item.setUri(LbryUri.tryParse(cursor.getString(cursorIndex++)));
item.setClaimId(cursor.getString(cursorIndex++));
item.setClaimName(cursor.getString(cursorIndex++));
item.setCost(new BigDecimal(cursor.getDouble(cursorIndex++)));
item.setTitle(cursor.getString(cursorIndex++));
item.setPublisherClaimId(cursor.getString(cursorIndex++));
item.setPublisherName(cursor.getString(cursorIndex++));
item.setPublisherTitle(cursor.getString(cursorIndex++));
item.setThumbnailUrl(cursor.getString(cursorIndex++));
item.setDevice(cursor.getString(cursorIndex++));
item.setReleaseTime(cursor.getLong(cursorIndex++));
try {
item.setTimestamp(new SimpleDateFormat(Helper.ISO_DATE_FORMAT_PATTERN).parse(cursor.getString(cursorIndex)));
} catch (ParseException ex) {
// invalid timestamp (which shouldn't happen). Skip this item
continue;
}
history.add(item);
}
} finally {
Helper.closeCursor(cursor);
}
return history;
}
public static void createOrUpdateTag(Tag tag, SQLiteDatabase db) {
db.execSQL(SQL_INSERT_TAG, new Object[] { tag.getLowercaseName(), tag.isFollowed() ? 1 : 0 });
}

View file

@ -0,0 +1,5 @@
package io.lbry.browser.listener;
public interface DownloadActionListener {
void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress);
}

View file

@ -82,6 +82,9 @@ public class Claim {
private GenericMetadata value;
private LbryFile file; // associated file if it exists
// device it was viewed on (for view history)
private String device;
public static Claim claimFromOutput(JSONObject item) {
// we only need name, permanent_url, txid and nout
Claim claim = new Claim();
@ -93,6 +96,10 @@ public class Claim {
return claim;
}
public String getOutpoint() {
return String.format("%s:%d", txid, nout);
}
public boolean isFree() {
if (!(value instanceof StreamMetadata)) {
return true;
@ -246,6 +253,8 @@ public class Claim {
claim.setName(viewHistory.getClaimName());
claim.setValueType(TYPE_STREAM);
claim.setPermanentUrl(viewHistory.getUri().toString());
claim.setDevice(viewHistory.getDevice());
claim.setConfirmations(1);
StreamMetadata value = new StreamMetadata();
value.setTitle(viewHistory.getTitle());

View file

@ -9,9 +9,12 @@ import org.json.JSONObject;
import java.lang.reflect.Type;
import io.lbry.browser.utils.LbryUri;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class LbryFile {
private Claim.StreamMetadata metadata;
private long addedOn;
@ -20,6 +23,7 @@ public class LbryFile {
private int blobsRemaining;
private String channelClaimId;
private String channelName;
@EqualsAndHashCode.Include
private String claimId;
private String claimName;
private boolean completed;
@ -39,16 +43,50 @@ public class LbryFile {
private String streamName;
private String streamingUrl;
private String suggestedFileName;
private long timestamp;
private long totalBytes;
private long totalBytesLowerBound;
private String txid;
private long writtenBytes;
private Claim generatedClaim;
public Claim getClaim() {
if (generatedClaim != null) {
return generatedClaim;
}
generatedClaim = new Claim();
generatedClaim.setValueType(Claim.TYPE_STREAM);
generatedClaim.setPermanentUrl(LbryUri.tryParse(String.format("%s#%s", claimName, claimId)).toString());
generatedClaim.setClaimId(claimId);
generatedClaim.setName(claimName);
generatedClaim.setValue(metadata);
generatedClaim.setConfirmations(1);
generatedClaim.setTxid(txid);
generatedClaim.setNout(nout);
generatedClaim.setFile(this);
if (channelClaimId != null) {
Claim signingChannel = new Claim();
signingChannel.setClaimId(channelClaimId);
signingChannel.setName(channelName);
signingChannel.setPermanentUrl(LbryUri.tryParse(String.format("%s#%s", claimName, claimId)).toString());
generatedClaim.setSigningChannel(signingChannel);
}
return generatedClaim;
}
public static LbryFile fromJSONObject(JSONObject fileObject) {
String fileJson = fileObject.toString();
Type type = new TypeToken<LbryFile>(){}.getType();
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
LbryFile file = gson.fromJson(fileJson, type);
if (file.getMetadata() != null && file.getMetadata().getReleaseTime() == 0) {
file.getMetadata().setReleaseTime(file.getTimestamp());
}
return file;
}
}

View file

@ -5,6 +5,7 @@ import java.util.Date;
import io.lbry.browser.exceptions.LbryUriException;
import io.lbry.browser.utils.LbryUri;
import io.lbry.browser.utils.Lbryio;
import lombok.Data;
@Data
@ -39,9 +40,12 @@ public class ViewHistory {
Claim.StreamMetadata value = (Claim.StreamMetadata) metadata;
history.setReleaseTime(value.getReleaseTime());
if (value.getFee() != null) {
history.setCost(new BigDecimal(value.getFee().getAmount()));
history.setCost(claim.getActualCost(Lbryio.LBCUSDRate));
}
}
if (history.getReleaseTime() == 0) {
history.setReleaseTime(claim.getTimestamp());
}
Claim signingChannel = claim.getSigningChannel();
if (signingChannel != null) {

View file

@ -12,12 +12,18 @@ import io.lbry.browser.utils.Lbry;
public class FileListTask extends AsyncTask<Void, Void, List<LbryFile>> {
private String claimId;
private boolean downloads;
private int page;
private int pageSize;
private FileListResultHandler handler;
private View progressView;
private ApiCallException error;
public FileListTask(View progressView, FileListResultHandler handler) {
public FileListTask(int page, int pageSize, boolean downloads, View progressView, FileListResultHandler handler) {
this(null, progressView, handler);
this.page = page;
this.pageSize = pageSize;
this.downloads = downloads;
}
public FileListTask(String claimId, View progressView, FileListResultHandler handler) {
@ -30,7 +36,7 @@ public class FileListTask extends AsyncTask<Void, Void, List<LbryFile>> {
}
protected List<LbryFile> doInBackground(Void... params) {
try {
return Lbry.fileList(claimId);
return Lbry.fileList(claimId, downloads, page, pageSize);
} catch (ApiCallException ex) {
error = ex;
return null;
@ -40,7 +46,7 @@ public class FileListTask extends AsyncTask<Void, Void, List<LbryFile>> {
Helper.setViewVisibility(progressView, View.GONE);
if (handler != null) {
if (files != null) {
handler.onSuccess(files);
handler.onSuccess(files, files.size() < pageSize);
} else {
handler.onError(error);
}
@ -48,7 +54,7 @@ public class FileListTask extends AsyncTask<Void, Void, List<LbryFile>> {
}
public interface FileListResultHandler {
void onSuccess(List<LbryFile> files);
void onSuccess(List<LbryFile> files, boolean hasReachedEnd);
void onError(Exception error);
}
}

View file

@ -0,0 +1,46 @@
package io.lbry.browser.tasks.localdata;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.AsyncTask;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import io.lbry.browser.data.DatabaseHelper;
import io.lbry.browser.model.UrlSuggestion;
import io.lbry.browser.model.ViewHistory;
import io.lbry.browser.utils.Helper;
public class FetchViewHistoryTask extends AsyncTask<Void, Void, List<ViewHistory>> {
private DatabaseHelper dbHelper;
private FetchViewHistoryHandler handler;
private int pageSize;
private Date lastDate;
public FetchViewHistoryTask(Date lastDate, int pageSize, DatabaseHelper dbHelper, FetchViewHistoryHandler handler) {
this.lastDate = lastDate;
this.pageSize = pageSize;
this.dbHelper = dbHelper;
this.handler = handler;
}
protected List<ViewHistory> doInBackground(Void... params) {
try {
SQLiteDatabase db = dbHelper.getReadableDatabase();
return DatabaseHelper.getViewHistory(
lastDate == null ? null : new SimpleDateFormat(Helper.ISO_DATE_FORMAT_PATTERN).format(lastDate), pageSize, db);
} catch (SQLiteException ex) {
return new ArrayList<>();
}
}
protected void onPostExecute(List<ViewHistory> history) {
if (handler != null) {
handler.onSuccess(history, history.size() < pageSize);
}
}
public interface FetchViewHistoryHandler {
void onSuccess(List<ViewHistory> history, boolean hasReachedEnd);
}
}

View file

@ -1,4 +1,45 @@
package io.lbry.browser.tasks.localdata;
public class SaveViewHistoryTask {
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import io.lbry.browser.data.DatabaseHelper;
import io.lbry.browser.model.ViewHistory;
public class SaveViewHistoryTask extends AsyncTask<Void, Void, Boolean> {
private DatabaseHelper dbHelper;
private ViewHistory history;
private SaveViewHistoryHandler handler;
private Exception error;
public SaveViewHistoryTask(ViewHistory history, DatabaseHelper dbHelper, SaveViewHistoryHandler handler) {
this.history = history;
this.dbHelper = dbHelper;
this.handler = handler;
}
protected Boolean doInBackground(Void... params) {
try {
SQLiteDatabase db = dbHelper.getWritableDatabase();
DatabaseHelper.createOrUpdateViewHistoryItem(history, db);
} catch (Exception ex) {
error = ex;
return false;
}
return true;
}
protected void onPostExecute(Boolean result) {
if (handler != null) {
if (result) {
handler.onSuccess(history);
} else {
handler.onError(error);
}
}
}
public interface SaveViewHistoryHandler {
void onSuccess(ViewHistory item);
void onError(Exception error);
}
}

View file

@ -15,8 +15,12 @@ import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.offline.Download;
import com.google.android.material.snackbar.Snackbar;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -30,8 +34,10 @@ import io.lbry.browser.dialog.ContentFromDialogFragment;
import io.lbry.browser.dialog.ContentScopeDialogFragment;
import io.lbry.browser.dialog.ContentSortDialogFragment;
import io.lbry.browser.dialog.CustomizeTagsDialogFragment;
import io.lbry.browser.listener.DownloadActionListener;
import io.lbry.browser.listener.TagListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.LbryFile;
import io.lbry.browser.model.Tag;
import io.lbry.browser.tasks.claim.ClaimSearchTask;
import io.lbry.browser.tasks.FollowUnfollowTagTask;
@ -43,7 +49,7 @@ import io.lbry.browser.utils.Predefined;
import lombok.Getter;
// TODO: Similar code to FollowingFragment and Channel page fragment. Probably make common operations (sorting/filtering) into a control
public class AllContentFragment extends BaseFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
public class AllContentFragment extends BaseFragment implements DownloadActionListener, SharedPreferences.OnSharedPreferenceChangeListener {
@Getter
private boolean singleTagView;
@ -374,6 +380,7 @@ public class AllContentFragment extends BaseFragment implements SharedPreference
} else {
LbryAnalytics.setCurrentScreen(activity, "All Content", "AllContent");
}
activity.addDownloadActionListener(this);
}
PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this);
@ -384,7 +391,11 @@ public class AllContentFragment extends BaseFragment implements SharedPreference
}
public void onPause() {
PreferenceManager.getDefaultSharedPreferences(getContext()).unregisterOnSharedPreferenceChangeListener(this);
Context context = getContext();
if (context != null) {
((MainActivity) context).removeDownloadActionListener(this);
}
PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}
@ -491,4 +502,24 @@ public class AllContentFragment extends BaseFragment implements SharedPreference
fetchClaimSearchContent(true);
}
}
public void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) {
if ("abort".equals(downloadAction)) {
if (contentListAdapter != null) {
contentListAdapter.clearFileForClaimOrUrl(outpoint, uri);
}
return;
}
try {
JSONObject fileInfo = new JSONObject(fileInfoJson);
LbryFile claimFile = LbryFile.fromJSONObject(fileInfo);
String claimId = claimFile.getClaimId();
if (contentListAdapter != null) {
contentListAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri);
}
} catch (JSONException ex) {
// invalid file info for download
}
}
}

View file

@ -16,6 +16,9 @@ import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -27,14 +30,16 @@ import io.lbry.browser.R;
import io.lbry.browser.adapter.ClaimListAdapter;
import io.lbry.browser.dialog.ContentFromDialogFragment;
import io.lbry.browser.dialog.ContentSortDialogFragment;
import io.lbry.browser.listener.DownloadActionListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.LbryFile;
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;
import lombok.Setter;
public class ChannelContentFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener {
public class ChannelContentFragment extends Fragment implements DownloadActionListener, SharedPreferences.OnSharedPreferenceChangeListener {
@Setter
private String channelId;
@ -189,12 +194,20 @@ public class ChannelContentFragment extends Fragment implements SharedPreference
public void onResume() {
super.onResume();
PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this);
Context context = getContext();
if (context != null) {
((MainActivity) context).addDownloadActionListener(this);
}
PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this);
fetchClaimSearchContent();
}
public void onPause() {
PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this);
Context context = getContext();
if (context != null) {
((MainActivity) context).removeDownloadActionListener(this);
}
PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this);
super.onPause();
}
@ -300,4 +313,24 @@ public class ChannelContentFragment extends Fragment implements SharedPreference
fetchClaimSearchContent(true);
}
}
public void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) {
if ("abort".equals(downloadAction)) {
if (contentListAdapter != null) {
contentListAdapter.clearFileForClaimOrUrl(outpoint, uri);
}
return;
}
try {
JSONObject fileInfo = new JSONObject(fileInfoJson);
LbryFile claimFile = LbryFile.fromJSONObject(fileInfo);
String claimId = claimFile.getClaimId();
if (contentListAdapter != null) {
contentListAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri);
}
} catch (JSONException ex) {
// invalid file info for download
}
}
}

View file

@ -20,6 +20,9 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.snackbar.Snackbar;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -35,7 +38,9 @@ import io.lbry.browser.dialog.ContentFromDialogFragment;
import io.lbry.browser.dialog.ContentSortDialogFragment;
import io.lbry.browser.dialog.DiscoverDialogFragment;
import io.lbry.browser.exceptions.LbryUriException;
import io.lbry.browser.listener.DownloadActionListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.LbryFile;
import io.lbry.browser.model.lbryinc.Subscription;
import io.lbry.browser.tasks.lbryinc.ChannelSubscribeTask;
import io.lbry.browser.tasks.claim.ClaimListResultHandler;
@ -53,7 +58,9 @@ import io.lbry.browser.utils.Predefined;
public class FollowingFragment extends BaseFragment implements
FetchSubscriptionsTask.FetchSubscriptionsHandler,
ChannelItemSelectionListener, SharedPreferences.OnSharedPreferenceChangeListener {
ChannelItemSelectionListener,
DownloadActionListener,
SharedPreferences.OnSharedPreferenceChangeListener {
public static boolean resetClaimSearchContent;
private static final int SUGGESTED_PAGE_SIZE = 45;
@ -334,6 +341,7 @@ public class FollowingFragment extends BaseFragment implements
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
LbryAnalytics.setCurrentScreen(activity, "Subscriptions", "Subscriptions");
activity.addDownloadActionListener(this);
}
// check if subscriptions exist
@ -351,7 +359,11 @@ public class FollowingFragment extends BaseFragment implements
}
}
public void onPause() {
PreferenceManager.getDefaultSharedPreferences(getContext()).unregisterOnSharedPreferenceChangeListener(this);
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).removeDownloadActionListener(this);
}
PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}
public void fetchLoadedSubscriptions() {
@ -767,4 +779,24 @@ public class FollowingFragment extends BaseFragment implements
fetchClaimSearchContent(true);
}
}
public void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) {
if ("abort".equals(downloadAction)) {
if (contentListAdapter != null) {
contentListAdapter.clearFileForClaimOrUrl(outpoint, uri);
}
return;
}
try {
JSONObject fileInfo = new JSONObject(fileInfoJson);
LbryFile claimFile = LbryFile.fromJSONObject(fileInfo);
String claimId = claimFile.getClaimId();
if (contentListAdapter != null) {
contentListAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri);
}
} catch (JSONException ex) {
// invalid file info for download
}
}
}

View file

@ -0,0 +1,423 @@
package io.lbry.browser.ui.library;
import android.content.Context;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import io.lbry.browser.MainActivity;
import io.lbry.browser.R;
import io.lbry.browser.adapter.ClaimListAdapter;
import io.lbry.browser.data.DatabaseHelper;
import io.lbry.browser.listener.DownloadActionListener;
import io.lbry.browser.listener.SdkStatusListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.LbryFile;
import io.lbry.browser.model.ViewHistory;
import io.lbry.browser.tasks.file.FileListTask;
import io.lbry.browser.tasks.localdata.FetchViewHistoryTask;
import io.lbry.browser.ui.BaseFragment;
import io.lbry.browser.utils.Helper;
import io.lbry.browser.utils.Lbry;
import io.lbry.browser.utils.LbryAnalytics;
public class LibraryFragment extends BaseFragment implements DownloadActionListener, SdkStatusListener {
private static final int FILTER_DOWNLOADS = 1;
private static final int FILTER_HISTORY = 2;
private static final int PAGE_SIZE = 50;
private int currentFilter;
private List<LbryFile> currentFiles;
private View layoutSdkInitializing;
private RecyclerView contentList;
private ClaimListAdapter contentListAdapter;
private ProgressBar listLoading;
private TextView linkFilterDownloads;
private TextView linkFilterHistory;
private View layoutListEmpty;
private TextView textListEmpty;
private int currentPage;
private Date lastDate;
private boolean listReachedEnd;
private CardView cardStats;
private TextView linkStats;
private TextView linkHide;
private View viewStatsDistribution;
private View viewVideoStatsBar;
private View viewAudioStatsBar;
private View viewImageStatsBar;
private View viewOtherStatsBar;
private TextView textStatsTotalSize;
private TextView textStatsTotalSizeUnits;
private TextView textStatsVideoSize;
private TextView textStatsAudioSize;
private TextView textStatsImageSize;
private TextView textStatsOtherSize;
private View legendVideo;
private View legendAudio;
private View legendImage;
private View legendOther;
private long totalBytes;
private long totalVideoBytes;
private long totalAudioBytes;
private long totalImageBytes;
private long totalOtherBytes;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_library, container, false);
layoutSdkInitializing = root.findViewById(R.id.container_library_sdk_initializing);
LinearLayoutManager llm = new LinearLayoutManager(getContext());
contentList = root.findViewById(R.id.library_list);
contentList.setLayoutManager(llm);
listLoading = root.findViewById(R.id.library_list_loading);
linkFilterDownloads = root.findViewById(R.id.library_filter_link_downloads);
linkFilterHistory = root.findViewById(R.id.library_filter_link_history);
layoutListEmpty = root.findViewById(R.id.library_empty_container);
textListEmpty = root.findViewById(R.id.library_list_empty_text);
currentFilter = FILTER_DOWNLOADS;
linkFilterDownloads.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showDownloads();
}
});
linkFilterHistory.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showHistory();
}
});
// stats
linkStats = root.findViewById(R.id.library_show_stats);
linkHide = root.findViewById(R.id.library_hide_stats);
cardStats = root.findViewById(R.id.library_storage_stats_card);
viewStatsDistribution = root.findViewById(R.id.library_storage_stat_distribution);
viewVideoStatsBar = root.findViewById(R.id.library_storage_stat_video_bar);
viewAudioStatsBar = root.findViewById(R.id.library_storage_stat_audio_bar);
viewImageStatsBar = root.findViewById(R.id.library_storage_stat_image_bar);
viewOtherStatsBar = root.findViewById(R.id.library_storage_stat_other_bar);
textStatsTotalSize = root.findViewById(R.id.library_storage_stat_used);
textStatsTotalSizeUnits = root.findViewById(R.id.library_storage_stat_unit);
textStatsVideoSize = root.findViewById(R.id.library_storage_stat_video_size);
textStatsAudioSize = root.findViewById(R.id.library_storage_stat_audio_size);
textStatsImageSize = root.findViewById(R.id.library_storage_stat_image_size);
textStatsOtherSize = root.findViewById(R.id.library_storage_stat_other_size);
legendVideo = root.findViewById(R.id.library_storage_legend_video);
legendAudio = root.findViewById(R.id.library_storage_legend_audio);
legendImage = root.findViewById(R.id.library_storage_legend_image);
legendOther = root.findViewById(R.id.library_storage_legend_other);
linkStats.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
updateStats();
cardStats.setVisibility(View.VISIBLE);
checkStatsLink();
}
});
linkHide.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
cardStats.setVisibility(View.GONE);
checkStatsLink();
}
});
return root;
}
public void onResume() {
super.onResume();
Context context = getContext();
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
LbryAnalytics.setCurrentScreen(activity, "Library", "Library");
activity.addDownloadActionListener(this);
}
layoutSdkInitializing.setVisibility(
!Lbry.SDK_READY && currentFilter == FILTER_DOWNLOADS ? View.VISIBLE : View.GONE);
if (!Lbry.SDK_READY) {
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
activity.addSdkStatusListener(this);
}
} else {
onSdkReady();
}
}
public void onPause() {
Context context = getContext();
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
activity.removeSdkStatusListener(this);
activity.removeDownloadActionListener(this);
}
super.onPause();
}
public void onSdkReady() {
layoutSdkInitializing.setVisibility(View.GONE);
if (currentFilter == FILTER_DOWNLOADS) {
showDownloads();
} else if (currentFilter == FILTER_HISTORY) {
showHistory();
}
}
private void showDownloads() {
currentFilter = FILTER_DOWNLOADS;
linkFilterDownloads.setTypeface(null, Typeface.BOLD);
linkFilterHistory.setTypeface(null, Typeface.NORMAL);
if (contentListAdapter != null) {
contentListAdapter.clearItems();
contentListAdapter.setCanEnterSelectionMode(true);
}
checkStatsLink();
layoutSdkInitializing.setVisibility(Lbry.SDK_READY ? View.GONE : View.VISIBLE);
currentPage = 1;
if (Lbry.SDK_READY) {
fetchDownloads();
}
}
private void showHistory() {
currentFilter = FILTER_HISTORY;
linkFilterDownloads.setTypeface(null, Typeface.NORMAL);
linkFilterHistory.setTypeface(null, Typeface.BOLD);
if (contentListAdapter != null) {
contentListAdapter.clearItems();
contentListAdapter.setCanEnterSelectionMode(false);
}
cardStats.setVisibility(View.GONE);
checkStatsLink();
layoutSdkInitializing.setVisibility(View.GONE);
lastDate = null;
fetchHistory();
}
private void initContentListAdapter(List<Claim> claims) {
contentListAdapter = new ClaimListAdapter(claims, getContext());
contentListAdapter.setCanEnterSelectionMode(true);
contentListAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() {
@Override
public void onClaimClicked(Claim claim) {
Context context = getContext();
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) getContext();
if (claim.getName().startsWith("@")) {
activity.openChannelUrl(claim.getPermanentUrl());
} else {
MainActivity.openFileUrl(claim.getPermanentUrl(), context);
}
}
}
});
}
private void fetchDownloads() {
Helper.setViewVisibility(linkStats, View.GONE);
Helper.setViewVisibility(layoutListEmpty, View.GONE);
FileListTask task = new FileListTask(currentPage, PAGE_SIZE, true, listLoading, new FileListTask.FileListResultHandler() {
@Override
public void onSuccess(List<LbryFile> files, boolean hasReachedEnd) {
listReachedEnd = hasReachedEnd;
List<LbryFile> filteredFiles = Helper.filterDownloads(files);
List<Claim> claims = Helper.claimsFromFiles(filteredFiles);
addFiles(filteredFiles);
updateStats();
checkStatsLink();
if (contentListAdapter == null) {
initContentListAdapter(claims);
} else {
contentListAdapter.addItems(claims);
}
contentList.setAdapter(contentListAdapter);
checkListEmpty();
}
@Override
public void onError(Exception error) {
// pass
checkStatsLink();
checkListEmpty();
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void fetchHistory() {
Helper.setViewVisibility(layoutListEmpty, View.GONE);
DatabaseHelper dbHelper = DatabaseHelper.getInstance();
if (dbHelper != null) {
FetchViewHistoryTask task = new FetchViewHistoryTask(lastDate, PAGE_SIZE, dbHelper, new FetchViewHistoryTask.FetchViewHistoryHandler() {
@Override
public void onSuccess(List<ViewHistory> history, boolean hasReachedEnd) {
listReachedEnd = hasReachedEnd;
List<Claim> claims = Helper.claimsFromViewHistory(history);
if (contentListAdapter == null) {
initContentListAdapter(claims);
} else {
contentListAdapter.addItems(claims);
}
contentList.setAdapter(contentListAdapter);
checkListEmpty();
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
checkListEmpty();
}
}
public void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) {
if ("abort".equals(downloadAction)) {
if (contentListAdapter != null) {
contentListAdapter.clearFileForClaimOrUrl(outpoint, uri, currentFilter == FILTER_DOWNLOADS);
}
return;
}
try {
JSONObject fileInfo = new JSONObject(fileInfoJson);
LbryFile claimFile = LbryFile.fromJSONObject(fileInfo);
String claimId = claimFile.getClaimId();
if (contentListAdapter != null) {
contentListAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri, true);
}
} catch (JSONException ex) {
// invalid file info for download
}
}
private void checkListEmpty() {
layoutListEmpty.setVisibility(contentListAdapter == null || contentListAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
textListEmpty.setText(currentFilter == FILTER_DOWNLOADS ? R.string.library_no_downloads : R.string.library_no_history);
}
private void addFiles(List<LbryFile> files) {
if (currentFiles == null) {
currentFiles = new ArrayList<>();
}
for (LbryFile file : files) {
if (!currentFiles.contains(file)) {
currentFiles.add(file);
}
}
}
private void updateStats() {
totalBytes = 0;
totalVideoBytes = 0;
totalAudioBytes = 0;
totalImageBytes = 0;
totalOtherBytes = 0;
if (currentFiles != null) {
for (LbryFile file : currentFiles) {
long writtenBytes = file.getWrittenBytes();
String mime = file.getMimeType();
if (mime != null) {
if (mime.startsWith("video/")) {
totalVideoBytes += writtenBytes;
} else if (mime.startsWith("audio/")) {
totalAudioBytes += writtenBytes;
} else if (mime.startsWith("image/")) {
totalImageBytes += writtenBytes;
} else {
totalOtherBytes += writtenBytes;
}
}
totalBytes += writtenBytes;
}
}
renderStats();
}
private void renderStats() {
String[] totalSizeParts = Helper.formatBytesParts(totalBytes, false);
textStatsTotalSize.setText(totalSizeParts[0]);
textStatsTotalSizeUnits.setText(totalSizeParts[1]);
viewStatsDistribution.setVisibility(totalBytes > 0 ? View.VISIBLE : View.GONE);
int percentVideo = normalizePercent((double) totalVideoBytes / (double) totalBytes * 100.0);
legendVideo.setVisibility(totalVideoBytes > 0 ? View.VISIBLE : View.GONE);
textStatsVideoSize.setText(Helper.formatBytes(totalVideoBytes, false));
applyLayoutWeight(viewVideoStatsBar, percentVideo);
int percentAudio = normalizePercent((double) totalAudioBytes / (double) totalBytes * 100.0);
legendAudio.setVisibility(totalAudioBytes > 0 ? View.VISIBLE : View.GONE);
textStatsAudioSize.setText(Helper.formatBytes(totalAudioBytes, false));
applyLayoutWeight(viewAudioStatsBar, percentAudio);
int percentImage = normalizePercent((double) totalImageBytes / (double) totalBytes * 100.0);
legendImage.setVisibility(totalImageBytes > 0 ? View.VISIBLE : View.GONE);
textStatsImageSize.setText(Helper.formatBytes(totalImageBytes, false));
applyLayoutWeight(viewImageStatsBar, percentImage);
int percentOther = normalizePercent((double) totalOtherBytes / (double) totalBytes * 100.0);
legendOther.setVisibility(totalOtherBytes > 0 ? View.VISIBLE : View.GONE);
textStatsOtherSize.setText(Helper.formatBytes(totalOtherBytes, false));
applyLayoutWeight(viewOtherStatsBar, percentOther);
// We have to get to 100 (or adjust the container accordingly)
int totalPercent = percentVideo + percentAudio + percentImage + percentOther;
((LinearLayout) viewStatsDistribution).setWeightSum(totalPercent);
}
private void applyLayoutWeight(View view, int weight) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) view.getLayoutParams();
params.weight = weight;
}
private static int normalizePercent(double value) {
if (value > 0 && value < 1) {
return 1;
}
return Double.valueOf(Math.floor(value)).intValue();
}
private void checkStatsLink() {
linkStats.setVisibility(cardStats.getVisibility() == View.VISIBLE ||
listLoading.getVisibility() == View.VISIBLE ||
currentFilter == FILTER_HISTORY ?
View.GONE : View.VISIBLE);
}
}

View file

@ -15,13 +15,20 @@ import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.offline.Download;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
import io.lbry.browser.MainActivity;
import io.lbry.browser.R;
import io.lbry.browser.adapter.ClaimListAdapter;
import io.lbry.browser.listener.DownloadActionListener;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.ClaimCacheKey;
import io.lbry.browser.model.LbryFile;
import io.lbry.browser.tasks.claim.ClaimListResultHandler;
import io.lbry.browser.tasks.claim.ClaimSearchTask;
import io.lbry.browser.tasks.LighthouseSearchTask;
@ -34,10 +41,10 @@ import io.lbry.browser.utils.LbryUri;
import lombok.Setter;
public class SearchFragment extends BaseFragment implements
ClaimListAdapter.ClaimListItemListener, SharedPreferences.OnSharedPreferenceChangeListener {
private ClaimListAdapter resultListAdapter;
ClaimListAdapter.ClaimListItemListener, DownloadActionListener, SharedPreferences.OnSharedPreferenceChangeListener {
private static final int PAGE_SIZE = 25;
private ClaimListAdapter resultListAdapter;
private ProgressBar loadingView;
private RecyclerView resultList;
private TextView noQueryView;
@ -95,6 +102,7 @@ public class SearchFragment extends BaseFragment implements
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
LbryAnalytics.setCurrentScreen(activity, "Search", "Search");
activity.addDownloadActionListener(this);
}
if (!Helper.isNullOrEmpty(currentQuery)) {
logSearch(currentQuery);
@ -106,7 +114,11 @@ public class SearchFragment extends BaseFragment implements
}
public void onPause() {
PreferenceManager.getDefaultSharedPreferences(getContext()).unregisterOnSharedPreferenceChangeListener(this);
Context context = getContext();
if (context != null) {
((MainActivity) context).removeDownloadActionListener(this);
}
PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}
@ -262,4 +274,24 @@ public class SearchFragment extends BaseFragment implements
search(currentQuery, currentFrom);
}
}
public void onDownloadAction(String downloadAction, String uri, String outpoint, String fileInfoJson, double progress) {
if ("abort".equals(downloadAction)) {
if (resultListAdapter != null) {
resultListAdapter.clearFileForClaimOrUrl(outpoint, uri);
}
return;
}
try {
JSONObject fileInfo = new JSONObject(fileInfoJson);
LbryFile claimFile = LbryFile.fromJSONObject(fileInfo);
String claimId = claimFile.getClaimId();
if (resultListAdapter != null) {
resultListAdapter.updateFileForClaimByIdOrUrl(claimFile, claimId, uri);
}
} catch (JSONException ex) {
// invalid file info for download
}
}
}

View file

@ -401,11 +401,11 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
LbryAnalytics.setCurrentScreen(activity, "Wallet", "Wallet");
}
Helper.setViewVisibility(layoutAccountRecommended, hasSkippedAccount() || Lbryio.isSignedIn() ? View.GONE : View.VISIBLE);
if (!Lbry.SDK_READY) {
if (context instanceof MainActivity) {
MainActivity activity = (MainActivity) context;
activity.addSdkStatusListener(this);
activity.addWalletBalanceListener(this);
}
checkReceiveAddress();
@ -441,7 +441,9 @@ public class WalletFragment extends BaseFragment implements SdkStatusListener, W
public void onSdkReady() {
Context context = getContext();
if (context instanceof MainActivity) {
((MainActivity) context).removeSdkStatusListener(this);
MainActivity activity = (MainActivity) context;
activity.syncWalletAndLoadPreferences();
activity.addWalletBalanceListener(this);
}
// update view

View file

@ -19,6 +19,7 @@ import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.text.method.LinkMovementMethod;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
@ -45,9 +46,12 @@ import io.lbry.browser.data.DatabaseHelper;
import io.lbry.browser.dialog.ContentFromDialogFragment;
import io.lbry.browser.dialog.ContentSortDialogFragment;
import io.lbry.browser.model.Claim;
import io.lbry.browser.model.LbryFile;
import io.lbry.browser.model.Tag;
import io.lbry.browser.model.UrlSuggestion;
import io.lbry.browser.model.ViewHistory;
import io.lbry.browser.tasks.localdata.SaveUrlHistoryTask;
import io.lbry.browser.tasks.localdata.SaveViewHistoryTask;
import okhttp3.MediaType;
public final class Helper {
@ -61,6 +65,7 @@ public final class Helper {
public static final int CONTENT_PAGE_SIZE = 25;
public static final double MIN_DEPOSIT = 0.05;
public static final String LBC_CURRENCY_FORMAT_PATTERN = "#,###.##";
public static final String FILE_SIZE_FORMAT_PATTERN = "#,###.#";
public static final DecimalFormat LBC_CURRENCY_FORMAT = new DecimalFormat(LBC_CURRENCY_FORMAT_PATTERN);
public static final DecimalFormat USD_CURRENCY_FORMAT = new DecimalFormat("#,##0.00");
public static final String EXPLORER_TX_PREFIX = "https://explorer.lbry.com/tx";
@ -121,6 +126,12 @@ public final class Helper {
}
}
public static void setViewProgress(ProgressBar progressBar, int progress) {
if (progressBar != null) {
progressBar.setProgress(progress);
}
}
public static void setViewText(TextView view, int stringResourceId) {
if (view != null) {
view.setText(stringResourceId);
@ -183,6 +194,43 @@ public final class Helper {
}
return String.format("%d:%02d", minutes, seconds);
}
public static String[] formatBytesParts(long bytes, boolean showTB) {
DecimalFormat formatter = new DecimalFormat(FILE_SIZE_FORMAT_PATTERN);
if (bytes < 1048576) {
// less than 1MB
return new String[] { formatter.format(bytes / 1024.0), "KB" };
}
if (bytes < 1073741824) {
// less than 1GB
return new String[] { formatter.format(bytes / (1024.0 * 1024.0)), "MB" };
}
if (showTB) {
if (bytes < (1073741824L * 1024L)) {
return new String[] { formatter.format(bytes / (1024.0 * 1024.0 * 1024.0)), "GB" };
}
return new String[] { formatter.format(bytes / (1024.0 * 1024.0 * 1024.0 * 1024.0)), "TB" };
}
return new String[] { formatter.format(bytes / (1024.0 * 1024.0 * 1024.0)), "GB" };
}
public static String formatBytes(long bytes, boolean showTB) {
DecimalFormat formatter = new DecimalFormat(FILE_SIZE_FORMAT_PATTERN);
if (bytes < 1048576) {
// less than 1MB
return String.format("%sKB", formatter.format(bytes / 1024.0));
}
if (bytes < 1073741824) {
// less than 1GB
return String.format("%sMB", formatter.format(bytes / (1024.0 * 1024.0)));
}
if (showTB) {
if (bytes < (1073741824L * 1024L)) {
return String.format("%sGB", formatter.format(bytes / (1024.0 * 1024.0 * 1024.0)));
}
return String.format("%sTB", formatter.format(bytes / (1024.0 * 1024.0 * 1024.0 * 1024.0)));
}
return String.format("%sGB", formatter.format(bytes / (1024.0 * 1024.0 * 1024.0)));
}
public static JSONObject getJSONObject(String name, JSONObject object) {
try {
@ -571,6 +619,33 @@ public final class Helper {
return filtered;
}
public static List<LbryFile> filterDownloads(List<LbryFile> files) {
List<LbryFile> filtered = new ArrayList<>();
for (int i = 0; i < files.size(); i++) {
LbryFile file = files.get(i);
if (!Helper.isNullOrEmpty(file.getDownloadPath())) {
filtered.add(file);
}
}
return filtered;
}
public static List<Claim> claimsFromFiles(List<LbryFile> files) {
List<Claim> claims = new ArrayList<>();
for (LbryFile file : files) {
claims.add(file.getClaim());
}
return claims;
}
public static List<Claim> claimsFromViewHistory(List<ViewHistory> history) {
List<Claim> claims = new ArrayList<>();
for (ViewHistory item : history) {
claims.add(Claim.fromViewHistory(item));
}
return claims;
}
public static void saveUrlHistory(String url, String title, int type) {
DatabaseHelper dbHelper = DatabaseHelper.getInstance();
if (dbHelper != null) {
@ -581,4 +656,12 @@ public final class Helper {
new SaveUrlHistoryTask(suggestion, dbHelper, null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
public static void saveViewHistory(String url, Claim claim) {
DatabaseHelper dbHelper = DatabaseHelper.getInstance();
if (dbHelper != null) {
ViewHistory viewHistory = ViewHistory.fromClaimWithUrlAndDeviceName(claim, url, getDeviceName());
new SaveViewHistoryTask(viewHistory, dbHelper, null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
}

View file

@ -307,12 +307,22 @@ public final class Lbry {
return file;
}
public static List<LbryFile> fileList(String claimId) throws ApiCallException {
public static List<LbryFile> fileList(String claimId, boolean downloads, int page, int pageSize) throws ApiCallException {
List<LbryFile> files = new ArrayList<>();
Map<String, Object> params = new HashMap<>();
if (!Helper.isNullOrEmpty(claimId)) {
params.put("claim_id", claimId);
}
/*if (downloads) {
params.put("file_name", null);
params.put("comparison", "ne");
}*/
if (page > 0) {
params.put("page", page);
}
if (pageSize > 0) {
params.put("page_size", pageSize);
}
try {
JSONObject result = (JSONObject) parseResponse(apiCall(METHOD_FILE_LIST, params));
JSONArray items = result.getJSONArray("items");

View file

@ -70,6 +70,7 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<LinearLayout
android:id="@+id/splash_view_loading_container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -89,6 +90,198 @@
android:layout_height="24dp"
android:layout_marginTop="48dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/splash_view_error_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical"
android:visibility="gone"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:text="@string/oops_something_went_wrong"
android:textSize="20sp"
android:textColor="@color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:layout_marginTop="8dp"
android:text="@string/startup_failed"
android:textColor="@color/white"
android:textFontWeight="300"
android:textSize="14sp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/startup_stage_icon_install_id"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:tint="@color/white"
android:src="@drawable/ic_check" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_gravity="center_vertical"
android:fontFamily="@font/inter"
android:text="@string/installation_id_loaded"
android:textColor="@color/white"
android:textFontWeight="300"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/startup_stage_icon_known_tags"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:tint="@color/white"
android:src="@drawable/ic_check" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_gravity="center_vertical"
android:fontFamily="@font/inter"
android:text="@string/known_tags_loaded"
android:textColor="@color/white"
android:textFontWeight="300"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/startup_stage_icon_exchange_rate"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:tint="@color/white"
android:src="@drawable/ic_check" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_gravity="center_vertical"
android:fontFamily="@font/inter"
android:text="@string/exchange_rate_loaded"
android:textColor="@color/white"
android:textFontWeight="300"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/startup_stage_icon_user_authenticated"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:tint="@color/white"
android:src="@drawable/ic_check" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_gravity="center_vertical"
android:fontFamily="@font/inter"
android:text="@string/user_authenticated"
android:textColor="@color/white"
android:textFontWeight="300"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/startup_stage_icon_install_new"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:tint="@color/white"
android:src="@drawable/ic_check" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_gravity="center_vertical"
android:fontFamily="@font/inter"
android:text="@string/installation_registered"
android:textColor="@color/white"
android:textFontWeight="300"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/startup_stage_icon_subscriptions_loaded"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:tint="@color/white"
android:src="@drawable/ic_check" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_gravity="center_vertical"
android:fontFamily="@font/inter"
android:text="@string/subscriptions_loaded"
android:textColor="@color/white"
android:textFontWeight="300"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/startup_stage_icon_subscriptions_resolved"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:tint="@color/white"
android:src="@drawable/ic_check" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_gravity="center_vertical"
android:fontFamily="@font/inter"
android:text="@string/subscriptions_resolved"
android:textColor="@color/white"
android:textFontWeight="300"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,347 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/pageBackground">
<androidx.cardview.widget.CardView
android:id="@+id/library_filter_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<TextView
android:id="@+id/library_filter_link_downloads"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:fontFamily="@font/inter"
android:text="@string/downloads"
android:textStyle="bold" />
<TextView
android:id="@+id/library_filter_link_history"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
android:layout_toRightOf="@id/library_filter_link_downloads"
android:fontFamily="@font/inter"
android:text="@string/history"
android:textSize="14sp" />
<ProgressBar
android:id="@+id/library_list_loading"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:visibility="gone" />
<TextView
android:id="@+id/library_show_stats"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:layout_alignParentRight="true"
android:fontFamily="@font/inter"
android:text="@string/stats"
android:textColor="@color/lbryGreen"
android:textFontWeight="300"
android:textSize="12sp"
android:visibility="gone" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/library_storage_stats_card"
android:layout_below="@id/library_filter_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/library_storage_stat_used"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:text="@string/zero"
android:textColor="@color/lbryGreen"
android:textFontWeight="300"
android:textSize="30sp" />
<TextView
android:id="@+id/library_storage_stat_unit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:fontFamily="@font/inter"
android:text="@string/mb"
android:textColor="@color/lbryGreen"
android:textFontWeight="300"
android:textSize="20sp" />
</LinearLayout>
<TextView
android:id="@+id/library_hide_stats"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginTop="4dp"
android:fontFamily="@font/inter"
android:text="@string/hide"
android:textFontWeight="300"
android:textSize="12sp" />
</RelativeLayout>
<LinearLayout
android:id="@+id/library_storage_stat_distribution"
android:layout_width="match_parent"
android:layout_height="10dp"
android:layout_marginTop="4dp"
android:orientation="horizontal"
android:weightSum="100"
android:visibility="gone">
<View
android:id="@+id/library_storage_stat_video_bar"
android:background="@color/statsVideo"
android:layout_weight="0"
android:layout_width="0dp"
android:layout_height="match_parent" />
<View
android:id="@+id/library_storage_stat_audio_bar"
android:background="@color/statsAudio"
android:layout_weight="0"
android:layout_width="0dp"
android:layout_height="match_parent" />
<View
android:id="@+id/library_storage_stat_image_bar"
android:background="@color/statsImage"
android:layout_weight="0"
android:layout_width="0dp"
android:layout_height="match_parent" />
<View
android:id="@+id/library_storage_stat_other_bar"
android:background="@color/statsOther"
android:layout_weight="0"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
<RelativeLayout
android:id="@+id/library_storage_legend_video"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:visibility="gone">
<View
android:id="@+id/library_storage_legend_video_icon"
android:background="@color/statsVideo"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_centerVertical="true" />
<TextView
android:layout_toRightOf="@id/library_storage_legend_video_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:fontFamily="@font/inter"
android:text="@string/video"
android:layout_marginLeft="4dp"
android:textFontWeight="300"
android:textSize="12sp" />
<TextView
android:id="@+id/library_storage_stat_video_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:fontFamily="@font/inter"
android:text="@string/zero_mb"
android:textFontWeight="300"
android:textSize="12sp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/library_storage_legend_audio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:visibility="gone">
<View
android:id="@+id/library_storage_legend_audio_icon"
android:background="@color/statsAudio"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_centerVertical="true" />
<TextView
android:layout_toRightOf="@id/library_storage_legend_audio_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:fontFamily="@font/inter"
android:text="@string/audio"
android:layout_marginLeft="4dp"
android:textFontWeight="300"
android:textSize="12sp" />
<TextView
android:id="@+id/library_storage_stat_audio_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:fontFamily="@font/inter"
android:text="@string/zero_mb"
android:textFontWeight="300"
android:textSize="12sp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/library_storage_legend_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:visibility="gone">
<View
android:id="@+id/library_storage_legend_image_icon"
android:background="@color/statsImage"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_centerVertical="true" />
<TextView
android:layout_toRightOf="@id/library_storage_legend_image_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:fontFamily="@font/inter"
android:text="@string/images"
android:layout_marginLeft="4dp"
android:textFontWeight="300"
android:textSize="12sp" />
<TextView
android:id="@+id/library_storage_stat_image_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:fontFamily="@font/inter"
android:text="@string/zero_mb"
android:textFontWeight="300"
android:textSize="12sp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/library_storage_legend_other"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:visibility="gone">
<View
android:id="@+id/library_storage_legend_other_icon"
android:background="@color/statsOther"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_centerVertical="true" />
<TextView
android:layout_toRightOf="@id/library_storage_legend_other_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:fontFamily="@font/inter"
android:text="@string/other"
android:layout_marginLeft="4dp"
android:textFontWeight="300"
android:textSize="12sp" />
<TextView
android:id="@+id/library_storage_stat_other_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:fontFamily="@font/inter"
android:text="@string/zero_mb"
android:textFontWeight="300"
android:textSize="12sp" />
</RelativeLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/library_list"
android:clipToPadding="false"
android:layout_below="@id/library_storage_stats_card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="12dp"
android:paddingBottom="16dp" />
<RelativeLayout
android:id="@+id/library_empty_container"
android:background="@color/pageBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="36dp">
<ImageView
android:layout_gravity="center_horizontal"
android:layout_width="160dp"
android:layout_height="300dp"
android:adjustViewBounds="true"
android:src="@drawable/gerbil_happy" />
<TextView
android:id="@+id/library_list_empty_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:fontFamily="@font/inter"
android:text="@string/library_no_downloads"
android:textAlignment="center"
android:textSize="16sp"
android:textFontWeight="300" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/container_library_sdk_initializing"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/library_filter_card"
android:visibility="gone">
<include layout="@layout/container_sdk_initializing" android:visibility="visible" />
</RelativeLayout>
</RelativeLayout>

View file

@ -168,24 +168,56 @@
android:textColor="@color/lbryGreen"
android:fontFamily="@font/inter"
android:textSize="12sp" />
<TextView
android:id="@+id/claim_publish_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:textSize="11sp"
android:textFontWeight="300" />
<TextView
android:id="@+id/claim_pending_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:text="@string/pending"
android:textSize="11sp"
android:textStyle="italic"
android:textFontWeight="300"
android:visibility="gone" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/claim_publish_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:textSize="11sp"
android:textFontWeight="300" />
<TextView
android:id="@+id/claim_pending_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/inter"
android:text="@string/pending"
android:textSize="11sp"
android:textStyle="italic"
android:textFontWeight="300"
android:visibility="gone" />
<TextView
android:id="@+id/claim_file_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:fontFamily="@font/inter"
android:textSize="11sp"
android:textFontWeight="300" />
</RelativeLayout>
<!-- download progress bar -->
<ProgressBar
android:id="@+id/claim_download_progress"
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_marginTop="4dp"
android:visibility="invisible"
style="?android:progressBarStyleHorizontal" />
<TextView
android:id="@+id/claim_view_device"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_gravity="end"
android:ellipsize="end"
android:fontFamily="@font/inter"
android:singleLine="true"
android:textColor="@color/lightGrey"
android:textSize="10sp"
android:textFontWeight="300" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>

View file

@ -48,6 +48,7 @@
<color name="uriDescBlue">#3971DB</color>
<color name="rewardDriverBlue">#2196f3</color>
<color name="statsVideo">@color/nextLbryGreen</color>
<color name="statsAudio">#F6A637</color>
<color name="statsImage">#FF4A7D</color>
<color name="statsOther">#26BCF7</color>

View file

@ -47,6 +47,7 @@
<color name="uriDescBlue">#3971DB</color>
<color name="rewardDriverBlue">#2196f3</color>
<color name="statsVideo">@color/nextLbryGreen</color>
<color name="statsAudio">#F6A637</color>
<color name="statsImage">#FF4A7D</color>
<color name="statsOther">#26BCF7</color>

View file

@ -93,6 +93,16 @@
<item quantity="other">%1$s followers</item>
</plurals>
<!-- Splash -->
<string name="oops_something_went_wrong">Oops! Something went wrong.</string>
<string name="installation_id_loaded">Loaded Installation ID.</string>
<string name="known_tags_loaded">Loaded local known and followed tags.</string>
<string name="exchange_rate_loaded">Loaded LBC/USD exchange rate.</string>
<string name="user_authenticated">User authenticated.</string>
<string name="installation_registered">Installation registered.</string>
<string name="subscriptions_loaded">Loaded subscriptions.</string>
<string name="subscriptions_resolved">Resolved subscriptions.</string>
<!-- Settings -->
<string name="user_interface">Content &amp; User interface</string>
<string name="other">Other</string>
@ -363,6 +373,21 @@
<string name="invite_link_copied">Invite link copied.</string>
<string name="invite_sent_to">Invite sent to %1$s</string>
<!-- Library -->
<string name="downloads">Downloads</string>
<string name="history">History</string>
<string name="library_no_downloads">You have not downloaded any content to this device.</string>
<string name="library_no_history">You have not viewed any content on this device.</string>
<string name="hide">Hide</string>
<string name="stats">Stats</string>
<string name="video">Video</string>
<string name="audio">Audio</string>
<string name="images">Images</string>
<string name="mb">MB</string>
<string name="kb">KB</string>
<string name="gb">GB</string>
<string name="zero_mb">0MB</string>
<!-- About -->
<string name="about_lbry">About LBRY</string>
<string name="content_freedom">Content Freedom</string>