diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4bb39341..ea66be0f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,9 @@ android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> + + @@ -39,7 +42,7 @@ android:supportsPictureInPicture="true" android:theme="@style/AppTheme.NoActionBar" android:launchMode="singleTask" - android:windowSoftInputMode="adjustPan"> + android:windowSoftInputMode="adjustResize"> diff --git a/app/src/main/java/io/lbry/browser/FileViewActivity.java b/app/src/main/java/io/lbry/browser/FileViewActivity.java index 5f7654cd..83a90954 100644 --- a/app/src/main/java/io/lbry/browser/FileViewActivity.java +++ b/app/src/main/java/io/lbry/browser/FileViewActivity.java @@ -1,7 +1,6 @@ package io.lbry.browser; import android.annotation.SuppressLint; -import android.app.Activity; import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.content.BroadcastReceiver; @@ -34,7 +33,6 @@ import android.widget.TextView; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.app.NavUtils; import androidx.core.content.ContextCompat; import androidx.core.widget.NestedScrollView; import androidx.preference.PreferenceManager; @@ -43,11 +41,15 @@ import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.github.chrisbanes.photoview.PhotoView; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.ext.cast.CastPlayer; +import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource; +import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.util.Util; @@ -84,6 +86,7 @@ import io.lbry.browser.model.ClaimCacheKey; import io.lbry.browser.model.Fee; import io.lbry.browser.model.LbryFile; import io.lbry.browser.model.Tag; +import io.lbry.browser.model.UrlSuggestion; import io.lbry.browser.model.lbryinc.Reward; import io.lbry.browser.model.lbryinc.Subscription; import io.lbry.browser.tasks.ReadTextFileTask; @@ -115,6 +118,8 @@ public class FileViewActivity extends AppCompatActivity { private static final int RELATED_CONTENT_SIZE = 16; private static boolean startingShareActivity; + private PlayerControlView castControlView; + private Player currentPlayer; private boolean backStackLost; private boolean loadingNewClaim; private boolean stopServiceReceived; @@ -163,11 +168,8 @@ public class FileViewActivity extends AppCompatActivity { instance = this; ClaimCacheKey key = new ClaimCacheKey(); key.setClaimId(claimId); - if (url.contains("#")) { - key.setPermanentUrl(url); // use the same url for the key so that we can match the key for any value that's the same - key.setCanonicalUrl(url); - key.setShortUrl(url); - } + 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); checkAndResetNowPlayingClaim(); @@ -181,6 +183,7 @@ public class FileViewActivity extends AppCompatActivity { currentUrl = url; logUrlEvent(url); + Helper.saveUrlHistory(url, claim != null ? claim.getTitle() : null, UrlSuggestion.TYPE_FILE); if (claim == null) { MainActivity.clearNowPlayingClaim(this); resolveUrl(url); @@ -218,6 +221,7 @@ public class FileViewActivity extends AppCompatActivity { } }; + castControlView = findViewById(R.id.file_view_cast_control_view); initUi(); onWalletBalanceUpdated(); renderClaim(); @@ -278,9 +282,7 @@ public class FileViewActivity extends AppCompatActivity { ClaimCacheKey key = new ClaimCacheKey(); key.setClaimId(newClaimId); if (!Helper.isNullOrEmpty(newUrl) && newUrl.contains("#")) { - key.setPermanentUrl(newUrl); - key.setCanonicalUrl(newUrl); - key.setShortUrl(newUrl); + key.setUrl(newUrl); } loadClaimForCacheKey(key, newUrl); } else if (!Helper.isNullOrEmpty(newUrl)) { @@ -290,9 +292,7 @@ public class FileViewActivity extends AppCompatActivity { onNewClaim(newUrl); ClaimCacheKey key = new ClaimCacheKey(); - key.setPermanentUrl(newUrl); - key.setCanonicalUrl(newUrl); - key.setShortUrl(newUrl); + key.setUrl(newUrl); loadClaimForCacheKey(key, newUrl); } } @@ -313,6 +313,7 @@ public class FileViewActivity extends AppCompatActivity { private void loadClaimForCacheKey(ClaimCacheKey key, String url) { if (Lbry.claimCache.containsKey(key)) { claim = Lbry.claimCache.get(key); + Helper.saveUrlHistory(url, claim.getTitle(), UrlSuggestion.TYPE_FILE); checkAndResetNowPlayingClaim(); if (claim.getFile() == null) { loadFile(); @@ -322,6 +323,7 @@ public class FileViewActivity extends AppCompatActivity { } renderClaim(); } else { + Helper.saveUrlHistory(url, null, UrlSuggestion.TYPE_FILE); findViewById(R.id.file_view_claim_display_area).setVisibility(View.INVISIBLE); MainActivity.clearNowPlayingClaim(this); resolveUrl(url); @@ -452,6 +454,8 @@ public class FileViewActivity extends AppCompatActivity { super.onResume(); MainActivity.mainActive = false; MainActivity.startingFileViewActivity = false; + + loadAndScheduleDurations(); if (Lbry.SDK_READY) { initFloatingWalletBalance(); } @@ -468,12 +472,25 @@ public class FileViewActivity extends AppCompatActivity { claim = claims.get(0); if (Claim.TYPE_REPOST.equalsIgnoreCase(claim.getValueType())) { claim = claim.getRepostedClaim(); - // cache the reposted claim too for subsequent loads - ClaimCacheKey key = ClaimCacheKey.fromClaim(claim); - Lbry.claimCache.put(key, claim); + Lbry.addClaimToCache(claim); + if (claim.getName().startsWith("@")) { + // this is a reposted channel, so finish this activity and launch the channel url + Intent intent = new Intent(MainActivity.ACTION_OPEN_CHANNEL_URL); + intent.putExtra("url", !Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl()); + sendBroadcast(intent); + + bringMainTaskToFront(); + finish(); + return; + } + } else { + Lbry.addClaimToCache(claim); } + Helper.saveUrlHistory(url, claim.getTitle(), UrlSuggestion.TYPE_FILE); + + // also save view history checkAndResetNowPlayingClaim(); loadFile(); renderClaim(); @@ -644,7 +661,14 @@ public class FileViewActivity extends AppCompatActivity { } }); - findViewById(R.id.player_toggle_full_screen).setOnClickListener(new View.OnClickListener() { + findViewById(R.id.player_toggle_cast).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + toggleCast(); + } + }); + + findViewById(R.id.player_toggle_fullscreen).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // check full screen mode @@ -664,7 +688,6 @@ public class FileViewActivity extends AppCompatActivity { Intent intent = new Intent(MainActivity.ACTION_OPEN_CHANNEL_URL); intent.putExtra("url", !Helper.isNullOrEmpty(publisher.getShortUrl()) ? publisher.getShortUrl() : publisher.getPermanentUrl()); sendBroadcast(intent); - bringMainTaskToFront(); finish(); } @@ -862,12 +885,12 @@ public class FileViewActivity extends AppCompatActivity { boolean newPlayerCreated = false; if (MainActivity.appPlayer == null) { MainActivity.appPlayer = new SimpleExoPlayer.Builder(this).build(); + MainActivity.castPlayer = new CastPlayer(MainActivity.castContext); newPlayerCreated = true; - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } - + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); PlayerView view = findViewById(R.id.file_view_exoplayer_view); view.setPlayer(MainActivity.appPlayer); if (MainActivity.nowPlayingClaim != null && @@ -881,6 +904,20 @@ public class FileViewActivity extends AppCompatActivity { showBuffering(); MainActivity.appPlayer.addListener(fileViewPlayerListener); + MainActivity.castPlayer.addListener(fileViewPlayerListener); + MainActivity.castPlayer.setSessionAvailabilityListener(new SessionAvailabilityListener() { + @Override + public void onCastSessionAvailable() { + setCurrentPlayer(MainActivity.castPlayer); + } + + @Override + public void onCastSessionUnavailable() { + setCurrentPlayer(MainActivity.appPlayer); + } + }); + + castControlView.setPlayer(MainActivity.castPlayer); MainActivity.setNowPlayingClaim(claim, FileViewActivity.this); String userAgent = Util.getUserAgent(this, getString(R.string.app_name)); @@ -893,6 +930,47 @@ public class FileViewActivity extends AppCompatActivity { MainActivity.appPlayer.prepare(mediaSource, true, true); } + private void setCurrentPlayer(Player currentPlayer) { + if (this.currentPlayer == currentPlayer) { + return; + } + + // View management. + if (currentPlayer == MainActivity.appPlayer) { + //localPlayerView.setVisibility(View.VISIBLE); + castControlView.hide(); + ((ImageView) findViewById(R.id.player_image_cast_toggle)).setImageResource(R.drawable.ic_cast); + } else /* currentPlayer == castPlayer */ { + castControlView.show(); + ((ImageView) findViewById(R.id.player_image_cast_toggle)).setImageResource(R.drawable.ic_cast_connected); + } + + // Player state management. + long playbackPositionMs = C.TIME_UNSET; + int windowIndex = C.INDEX_UNSET; + boolean playWhenReady = false; + + Player previousPlayer = this.currentPlayer; + if (previousPlayer != null) { + // Save state from the previous player. + int playbackState = previousPlayer.getPlaybackState(); + if (playbackState != Player.STATE_ENDED) { + playbackPositionMs = previousPlayer.getCurrentPosition(); + playWhenReady = previousPlayer.getPlayWhenReady(); + } + previousPlayer.stop(true); + } + + this.currentPlayer = currentPlayer; + + // Media queue management. + /*if (currentPlayer == exoPlayer) { + exoPlayer.prepare(concatenatingMediaSource); + }*/ + currentPlayer.seekTo(playbackPositionMs); + currentPlayer.setPlayWhenReady(true); + } + private void resetViewCount() { TextView textViewCount = findViewById(R.id.file_view_view_count); Helper.setViewText(textViewCount, null); @@ -1179,7 +1257,7 @@ public class FileViewActivity extends AppCompatActivity { if (claim.getName().startsWith("@")) { // opening a channel Intent intent = new Intent(MainActivity.ACTION_OPEN_CHANNEL_URL); - intent.putExtra("url", !Helper.isNullOrEmpty(claim.getShortUrl()) ? claim.getShortUrl() : claim.getPermanentUrl()); + intent.putExtra("url", claim.getPermanentUrl()); sendBroadcast(intent); bringMainTaskToFront(); finish(); @@ -1409,6 +1487,18 @@ public class FileViewActivity extends AppCompatActivity { Helper.setViewText(findViewById(R.id.player_duration_total), Helper.formatDuration(Double.valueOf(totalDuration / 1000.0).longValue())); } + private void loadAndScheduleDurations() { + if (MainActivity.appPlayer != null) { + if (totalDuration == 0) { + elapsedDuration = MainActivity.appPlayer.getCurrentPosition(); + totalDuration = MainActivity.appPlayer.getDuration(); + } + renderElapsedDuration(); + renderTotalDuration(); + scheduleElapsedPlayback(); + } + } + private void logPlay(String url, long startTimeMillis) { long timeToStartMillis = startTimeMillis > 0 ? System.currentTimeMillis() - startTimeMillis : 0; @@ -1554,6 +1644,19 @@ public class FileViewActivity extends AppCompatActivity { findViewById(R.id.floating_balance_main_container).setVisibility(View.VISIBLE); } + private void toggleCast() { + if (!MainActivity.castPlayer.isCastSessionAvailable()) { + showError(getString(R.string.no_cast_session_available)); + return; + } + + if (currentPlayer == MainActivity.appPlayer) { + setCurrentPlayer(MainActivity.castPlayer); + } else { + setCurrentPlayer(MainActivity.appPlayer); + } + } + private void onDownloadAborted() { downloadInProgress = false; @@ -1598,6 +1701,7 @@ public class FileViewActivity extends AppCompatActivity { final Set categories = baseIntent.getCategories(); if (categories != null && categories.contains(Intent.CATEGORY_LAUNCHER)) { task.moveToFront(); + finish(); return; } } diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index 793bf2a5..b9775219 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -36,7 +36,9 @@ 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.ui.PlayerView; +import com.google.android.gms.cast.framework.CastContext; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; import com.google.android.material.snackbar.Snackbar; @@ -105,6 +107,7 @@ import io.lbry.browser.tasks.lbryinc.FetchRewardsTask; import io.lbry.browser.tasks.LighthouseAutoCompleteTask; import io.lbry.browser.tasks.MergeSubscriptionsTask; import io.lbry.browser.tasks.claim.ResolveTask; +import io.lbry.browser.tasks.localdata.FetchRecentUrlHistoryTask; import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler; import io.lbry.browser.tasks.wallet.LoadSharedUserStateTask; import io.lbry.browser.tasks.wallet.SaveSharedUserStateTask; @@ -140,6 +143,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private Map specialRouteFragmentClassMap; private boolean inPictureInPictureMode; public static SimpleExoPlayer appPlayer; + public static CastContext castContext; + public static CastPlayer castPlayer; public static Claim nowPlayingClaim; public static boolean startingFilePickerActivity = false; public static boolean startingShareActivity = false; @@ -225,7 +230,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private NavigationMenuAdapter navMenuAdapter; private UrlSuggestionListAdapter urlSuggestionListAdapter; - private List recentHistory; + private List recentUrlHistory; private boolean hasLoadedFirstBalance; // broadcast receivers @@ -295,7 +300,6 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener initKeyStore(); loadAuthToken(); - dbHelper = new DatabaseHelper(this); if (!isDarkMode()) { getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } @@ -315,6 +319,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener }); super.onCreate(savedInstanceState); + castContext = CastContext.getSharedInstance(this); + dbHelper = new DatabaseHelper(this); checkNotificationOpenIntent(getIntent()); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); @@ -542,9 +548,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private Claim getCachedClaimForUrl(String url) { ClaimCacheKey key = new ClaimCacheKey(); - key.setCanonicalUrl(url); - key.setPermanentUrl(url); - key.setShortUrl(url); + key.setUrl(url); return Lbry.claimCache.containsKey(key) ? Lbry.claimCache.get(key) : null; } @@ -881,19 +885,25 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener for (int i = 0; i < claims.size(); i++) { // build a simple url from the claim for matching Claim claim = claims.get(i); + Claim actualClaim = claim; + boolean isRepost = false; + if (Claim.TYPE_REPOST.equalsIgnoreCase(claim.getValueType())) { + actualClaim = claim.getRepostedClaim(); + isRepost = true; + } if (Helper.isNullOrEmpty(claim.getName())) { continue; } LbryUri simpleUrl = new LbryUri(); - if (claim.getName().startsWith("@")) { + if (actualClaim.getName().startsWith("@") && !isRepost) { // channel - simpleUrl.setChannelName(claim.getName()); + simpleUrl.setChannelName(actualClaim.getName()); } else { simpleUrl.setStreamName(claim.getName()); } - urlSuggestionListAdapter.setClaimForUrl(simpleUrl, claim); + urlSuggestionListAdapter.setClaimForUrl(simpleUrl, actualClaim); } urlSuggestionListAdapter.notifyDataSetChanged(); } @@ -909,10 +919,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private void displayUrlSuggestionsForNoInput() { urlSuggestionListAdapter.clear(); - List blankSuggestions = buildDefaultSuggestionsForBlankUrl(); - urlSuggestionListAdapter.addUrlSuggestions(blankSuggestions); - List urls = urlSuggestionListAdapter.getItemUrls(); - resolveUrlSuggestions(urls); + loadDefaultSuggestionsForBlankUrl(); } private void handleUriInputChanged(String text) { @@ -948,25 +955,48 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - private List buildDefaultSuggestionsForBlankUrl() { - List suggestions = new ArrayList<>(); - if (recentHistory != null && recentHistory.size() > 0) { - // show recent history if avaiable - suggestions = new ArrayList<>(recentHistory); - } else { - try { - suggestions.add(new UrlSuggestion( - UrlSuggestion.TYPE_FILE, "What is LBRY?", LbryUri.parse("lbry://what#19b9c243bea0c45175e6a6027911abbad53e983e"))); - suggestions.add(new UrlSuggestion( - UrlSuggestion.TYPE_CHANNEL, "LBRYCast", LbryUri.parse("lbry://@lbrycast#4c29f8b013adea4d5cca1861fb2161d5089613ea"))); - suggestions.add(new UrlSuggestion( - UrlSuggestion.TYPE_CHANNEL, "The LBRY Channel", LbryUri.parse("lbry://@lbry#3fda836a92faaceedfe398225fb9b2ee2ed1f01a"))); + private void loadDefaultSuggestionsForBlankUrl() { + if (recentUrlHistory != null && recentUrlHistory.size() > 0) { + urlSuggestionListAdapter.addUrlSuggestions(recentUrlHistory); + } + + FetchRecentUrlHistoryTask task = new FetchRecentUrlHistoryTask(DatabaseHelper.getInstance(), new FetchRecentUrlHistoryTask.FetchRecentUrlHistoryHandler() { + @Override + public void onSuccess(List recentHistory) { + List suggestions = new ArrayList<>(recentHistory); + List lbrySuggestions = buildLbryUrlSuggestions(); + if (suggestions.size() < 10) { + for (int i = suggestions.size(), j = 0; i < 10 && j < lbrySuggestions.size(); i++, j++) { + suggestions.add(lbrySuggestions.get(j)); + } + } else if (suggestions.size() == 0) { + suggestions.addAll(lbrySuggestions); + } + for (UrlSuggestion suggestion : suggestions) { suggestion.setUseTextAsDescription(true); } - } catch (LbryUriException ex) { - // pass + + recentUrlHistory = new ArrayList<>(suggestions); + urlSuggestionListAdapter.clear(); + urlSuggestionListAdapter.addUrlSuggestions(recentUrlHistory); + List urls = urlSuggestionListAdapter.getItemUrls(); + resolveUrlSuggestions(urls); } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private List buildLbryUrlSuggestions() { + List suggestions = new ArrayList<>(); + suggestions.add(new UrlSuggestion( + UrlSuggestion.TYPE_FILE, "What is LBRY?", LbryUri.tryParse("lbry://what#19b9c243bea0c45175e6a6027911abbad53e983e"))); + suggestions.add(new UrlSuggestion( + UrlSuggestion.TYPE_CHANNEL, "LBRYCast", LbryUri.tryParse("lbry://@lbrycast#4c29f8b013adea4d5cca1861fb2161d5089613ea"))); + suggestions.add(new UrlSuggestion( + UrlSuggestion.TYPE_CHANNEL, "The LBRY Channel", LbryUri.tryParse("lbry://@lbry#3fda836a92faaceedfe398225fb9b2ee2ed1f01a"))); + for (UrlSuggestion suggestion : suggestions) { + suggestion.setUseTextAsDescription(true); } return suggestions; } @@ -975,7 +1005,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener List suggestions = new ArrayList(); if (LbryUri.PROTO_DEFAULT.equalsIgnoreCase(text)) { - return buildDefaultSuggestionsForBlankUrl(); + loadDefaultSuggestionsForBlankUrl(); + return recentUrlHistory != null ? recentUrlHistory : new ArrayList<>(); } // First item is always search diff --git a/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java index 1ebf542e..cc2a09ea 100644 --- a/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java +++ b/app/src/main/java/io/lbry/browser/adapter/UrlSuggestionListAdapter.java @@ -16,6 +16,7 @@ import io.lbry.browser.exceptions.LbryUriException; import io.lbry.browser.model.Claim; import io.lbry.browser.model.UrlSuggestion; import io.lbry.browser.ui.controls.SolidIconView; +import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.LbryUri; import lombok.Setter; @@ -96,7 +97,7 @@ public class UrlSuggestionListAdapter extends RecyclerView.Adapter> { } protected List doInBackground(Void... params) { try { - return Lbry.claimSearch(options, connectionString); + return Helper.filterInvalidReposts(Lbry.claimSearch(options, connectionString)); } catch (ApiCallException ex) { error = ex; return null; diff --git a/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java b/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java index 5cab8c93..120c36b7 100644 --- a/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/claim/ResolveTask.java @@ -34,7 +34,7 @@ public class ResolveTask extends AsyncTask> { } protected List doInBackground(Void... params) { try { - return Lbry.resolve(urls, connectionString); + return Helper.filterInvalidReposts(Lbry.resolve(urls, connectionString)); } catch (ApiCallException ex) { error = ex; return null; diff --git a/app/src/main/java/io/lbry/browser/tasks/localdata/FetchRecentUrlHistoryTask.java b/app/src/main/java/io/lbry/browser/tasks/localdata/FetchRecentUrlHistoryTask.java new file mode 100644 index 00000000..852e87ae --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/localdata/FetchRecentUrlHistoryTask.java @@ -0,0 +1,37 @@ +package io.lbry.browser.tasks.localdata; + +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.os.AsyncTask; + +import java.util.ArrayList; +import java.util.List; + +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.model.UrlSuggestion; + +public class FetchRecentUrlHistoryTask extends AsyncTask> { + private DatabaseHelper dbHelper; + private FetchRecentUrlHistoryHandler handler; + public FetchRecentUrlHistoryTask(DatabaseHelper dbHelper, FetchRecentUrlHistoryHandler handler) { + this.dbHelper = dbHelper; + this.handler = handler; + } + protected List doInBackground(Void... params) { + try { + SQLiteDatabase db = dbHelper.getReadableDatabase(); + return DatabaseHelper.getRecentHistory(db); + } catch (SQLiteException ex) { + return new ArrayList<>(); + } + } + protected void onPostExecute(List recentHistory) { + if (handler != null) { + handler.onSuccess(recentHistory); + } + } + + public interface FetchRecentUrlHistoryHandler { + void onSuccess(List recentHistory); + } +} diff --git a/app/src/main/java/io/lbry/browser/tasks/localdata/CreateUrlHistoryTask.java b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveUrlHistoryTask.java similarity index 64% rename from app/src/main/java/io/lbry/browser/tasks/localdata/CreateUrlHistoryTask.java rename to app/src/main/java/io/lbry/browser/tasks/localdata/SaveUrlHistoryTask.java index f74683eb..a5778984 100644 --- a/app/src/main/java/io/lbry/browser/tasks/localdata/CreateUrlHistoryTask.java +++ b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveUrlHistoryTask.java @@ -9,21 +9,21 @@ import io.lbry.browser.data.DatabaseHelper; import io.lbry.browser.model.UrlSuggestion; import io.lbry.browser.tasks.GenericTaskHandler; -public class CreateUrlHistoryTask extends AsyncTask { - private Context context; +public class SaveUrlHistoryTask extends AsyncTask { + private DatabaseHelper dbHelper; private UrlSuggestion suggestion; - private GenericTaskHandler handler; + private SaveUrlHistoryHandler handler; private Exception error; - public CreateUrlHistoryTask(UrlSuggestion suggestion, Context context, GenericTaskHandler handler) { + public SaveUrlHistoryTask(UrlSuggestion suggestion, DatabaseHelper dbHelper, SaveUrlHistoryHandler handler) { this.suggestion = suggestion; - this.context = context; + this.dbHelper = dbHelper; this.handler = handler; } protected Boolean doInBackground(Void... params) { try { - SQLiteDatabase db = ((MainActivity) context).getDbHelper().getWritableDatabase(); + SQLiteDatabase db = dbHelper.getWritableDatabase(); DatabaseHelper.createOrUpdateUrlHistoryItem(suggestion.getText(), suggestion.getUri().toString(), suggestion.getType(), db); } catch (Exception ex) { error = ex; @@ -35,10 +35,15 @@ public class CreateUrlHistoryTask extends AsyncTask { protected void onPostExecute(Boolean result) { if (handler != null) { if (result) { - handler.onSuccess(); + handler.onSuccess(suggestion); } else { handler.onError(error); } } } + + public interface SaveUrlHistoryHandler { + void onSuccess(UrlSuggestion item); + void onError(Exception error); + } } diff --git a/app/src/main/java/io/lbry/browser/tasks/localdata/SaveViewHistoryTask.java b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveViewHistoryTask.java new file mode 100644 index 00000000..097d5a13 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/localdata/SaveViewHistoryTask.java @@ -0,0 +1,4 @@ +package io.lbry.browser.tasks.localdata; + +public class SaveViewHistoryTask { +} diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java index d08d2539..75d2a41b 100644 --- a/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelFragment.java @@ -31,10 +31,13 @@ import java.util.Map; import io.lbry.browser.MainActivity; import io.lbry.browser.R; +import io.lbry.browser.data.DatabaseHelper; import io.lbry.browser.dialog.SendTipDialogFragment; import io.lbry.browser.exceptions.LbryUriException; import io.lbry.browser.listener.FetchChannelsListener; import io.lbry.browser.model.Claim; +import io.lbry.browser.model.ClaimCacheKey; +import io.lbry.browser.model.UrlSuggestion; import io.lbry.browser.model.lbryinc.Subscription; import io.lbry.browser.tasks.lbryinc.ChannelSubscribeTask; import io.lbry.browser.tasks.claim.ClaimListResultHandler; @@ -296,13 +299,24 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen if (updateRequired) { resetSubCount(); if (!Helper.isNullOrEmpty(url)) { - resolveUrl(); + // check if the claim is already cached + ClaimCacheKey key = new ClaimCacheKey(); + key.setUrl(url); + if (Lbry.claimCache.containsKey(key)) { + claim = Lbry.claimCache.get(key); + } else { + resolveUrl(); + } } else if (claim == null) { // nothing at this location renderNothingAtLocation(); } } + if (!Helper.isNullOrEmpty(url)) { + Helper.saveUrlHistory(url, claim != null ? claim.getTitle() : null, UrlSuggestion.TYPE_CHANNEL); + } + if (claim != null) { renderClaim(); } @@ -315,6 +329,10 @@ public class ChannelFragment extends BaseFragment implements FetchChannelsListen public void onSuccess(List claims) { if (claims.size() > 0 && !Helper.isNullOrEmpty(claims.get(0).getClaimId())) { claim = claims.get(0); + if (!Helper.isNullOrEmpty(url)) { + Helper.saveUrlHistory(url, claim.getTitle(), UrlSuggestion.TYPE_CHANNEL); + } + renderClaim(); checkOwnChannel(); } else { diff --git a/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java b/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java index feeb6545..0b148416 100644 --- a/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/search/SearchFragment.java @@ -141,7 +141,7 @@ public class SearchFragment extends BaseFragment implements private void resolveFeaturedItem(String vanityUrl) { final ClaimCacheKey key = new ClaimCacheKey(); - key.setVanityUrl(vanityUrl); + key.setUrl(vanityUrl); if (Lbry.claimCache.containsKey(key)) { Claim cachedClaim = Lbry.claimCache.get(key); updateFeaturedItemFromResolvedClaim(cachedClaim); diff --git a/app/src/main/java/io/lbry/browser/utils/Helper.java b/app/src/main/java/io/lbry/browser/utils/Helper.java index 18673b39..4a7165d1 100644 --- a/app/src/main/java/io/lbry/browser/utils/Helper.java +++ b/app/src/main/java/io/lbry/browser/utils/Helper.java @@ -12,6 +12,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.ShapeDrawable; import android.net.Uri; +import android.os.AsyncTask; import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; @@ -40,10 +41,13 @@ import java.util.Map; import java.util.Random; import io.lbry.browser.MainActivity; +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.Tag; +import io.lbry.browser.model.UrlSuggestion; +import io.lbry.browser.tasks.localdata.SaveUrlHistoryTask; import okhttp3.MediaType; public final class Helper { @@ -555,4 +559,26 @@ public final class Helper { textView.setMovementMethod(LinkMovementMethod.getInstance()); textView.setText(HtmlCompat.fromHtml(textView.getText().toString(), HtmlCompat.FROM_HTML_MODE_LEGACY)); } + + public static List filterInvalidReposts(List claims) { + List filtered = new ArrayList<>(); + for (Claim claim : claims) { + if (Claim.TYPE_REPOST.equalsIgnoreCase(claim.getValueType()) && claim.getRepostedClaim() == null) { + continue; + } + filtered.add(claim); + } + return filtered; + } + + public static void saveUrlHistory(String url, String title, int type) { + DatabaseHelper dbHelper = DatabaseHelper.getInstance(); + if (dbHelper != null) { + UrlSuggestion suggestion = new UrlSuggestion(); + suggestion.setUri(LbryUri.tryParse(url)); + suggestion.setType(type); + suggestion.setText(Helper.isNull(title) ? "" : title); + new SaveUrlHistoryTask(suggestion, dbHelper, null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } } diff --git a/app/src/main/java/io/lbry/browser/utils/Lbry.java b/app/src/main/java/io/lbry/browser/utils/Lbry.java index 3b793d6d..0982ff1d 100644 --- a/app/src/main/java/io/lbry/browser/utils/Lbry.java +++ b/app/src/main/java/io/lbry/browser/utils/Lbry.java @@ -250,9 +250,7 @@ public final class Lbry { Claim claim = Claim.fromJSONObject(result.getJSONObject(keys.next())); claims.add(claim); - // TODO: Add/remove/prune entries claim cache management - ClaimCacheKey key = ClaimCacheKey.fromClaim(claim); - claimCache.put(key, claim); + addClaimToCache(claim); } } } catch (LbryRequestException | LbryResponseException | JSONException ex) { @@ -396,8 +394,7 @@ public final class Lbry { Claim claim = Claim.fromJSONObject(items.getJSONObject(i)); claims.add(claim); - ClaimCacheKey key = ClaimCacheKey.fromClaim(claim); - claimCache.put(key, claim); + addClaimToCache(claim); } } @@ -452,4 +449,15 @@ public final class Lbry { } } } + + public static void addClaimToCache(Claim claim) { + ClaimCacheKey fullKey = ClaimCacheKey.fromClaim(claim); + ClaimCacheKey shortUrlKey = ClaimCacheKey.fromClaimShortUrl(claim); + ClaimCacheKey permanentUrlKey = ClaimCacheKey.fromClaimPermanentUrl(claim); + claimCache.put(fullKey, claim); + claimCache.put(permanentUrlKey, claim); + if (!Helper.isNullOrEmpty(shortUrlKey.getUrl())) { + claimCache.put(shortUrlKey, claim); + } + } } diff --git a/app/src/main/res/drawable-anydpi/ic_cast.xml b/app/src/main/res/drawable-anydpi/ic_cast.xml new file mode 100644 index 00000000..da0e952f --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_cast.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_cast_connected.xml b/app/src/main/res/drawable-anydpi/ic_cast_connected.xml new file mode 100644 index 00000000..491bf6c8 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_cast_connected.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/ic_cast.png b/app/src/main/res/drawable-hdpi/ic_cast.png new file mode 100644 index 00000000..5449b8f5 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_cast.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_cast_connected.png b/app/src/main/res/drawable-hdpi/ic_cast_connected.png new file mode 100644 index 00000000..55bddaad Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_cast_connected.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_cast.png b/app/src/main/res/drawable-mdpi/ic_cast.png new file mode 100644 index 00000000..60bb5f50 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_cast.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_cast_connected.png b/app/src/main/res/drawable-mdpi/ic_cast_connected.png new file mode 100644 index 00000000..b4b72ba3 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_cast_connected.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_cast.png b/app/src/main/res/drawable-xhdpi/ic_cast.png new file mode 100644 index 00000000..fdde81a4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_cast.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_cast_connected.png b/app/src/main/res/drawable-xhdpi/ic_cast_connected.png new file mode 100644 index 00000000..bcc195dc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_cast_connected.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_cast.png b/app/src/main/res/drawable-xxhdpi/ic_cast.png new file mode 100644 index 00000000..62fc53f7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_cast.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_cast_connected.png b/app/src/main/res/drawable-xxhdpi/ic_cast_connected.png new file mode 100644 index 00000000..a28df6de Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_cast_connected.png differ diff --git a/app/src/main/res/layout/activity_file_view.xml b/app/src/main/res/layout/activity_file_view.xml index 61767607..6afd90ef 100644 --- a/app/src/main/res/layout/activity_file_view.xml +++ b/app/src/main/res/layout/activity_file_view.xml @@ -162,9 +162,15 @@ android:layout_height="wrap_content" /> - + + - diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index 7a78d484..b147284d 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -20,9 +20,9 @@ android:id="@+id/url_suggestions_container" android:background="@color/pageBackground" android:elevation="6dp" + android:visibility="gone" android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone"> + android:layout_height="match_parent"> + + + + Delete file Are you sure you want to remove this file from your device? Failed to load %1$s. Please try again later. + There is no cast session available at this time. %1$s view %1$s views