From 1d1c761d3ffb821163c3f1d5a5de7dec85e8b090 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 30 Apr 2020 19:05:37 +0100 Subject: [PATCH] Finish Following and All Content views. Add customize your tags view. --- .../io/lbry/browser/FileViewActivity.java | 20 +- .../java/io/lbry/browser/MainActivity.java | 116 ++++- .../lbry/browser/adapter/TagListAdapter.java | 38 +- .../adapter/UrlSuggestionListAdapter.java | 16 +- .../io/lbry/browser/data/DatabaseHelper.java | 86 ++- .../dialog/ContentScopeDialogFragment.java | 96 ++++ .../dialog/CustomizeTagsDialogFragment.java | 211 ++++++++ .../browser/model/ClaimSearchCacheValue.java | 26 + .../io/lbry/browser/model/NavMenuItem.java | 3 +- .../main/java/io/lbry/browser/model/Tag.java | 19 +- .../io/lbry/browser/model/UrlSuggestion.java | 29 +- .../io/lbry/browser/tasks/LoadTagsTask.java | 59 +++ .../ui/allcontent/AllContentFragment.java | 126 ++++- .../ui/channel/ChannelContentFragment.java | 33 +- .../browser/ui/channel/ChannelFragment.java | 20 - .../ui/following/FollowingFragment.java | 89 +++- .../browser/ui/search/SearchFragment.java | 32 +- .../java/io/lbry/browser/utils/Helper.java | 42 +- .../main/java/io/lbry/browser/utils/Lbry.java | 16 +- .../java/io/lbry/browser/utils/LbryUri.java | 25 +- .../io/lbry/browser/utils/Predefined.java | 492 ++++++++++++++++++ app/src/main/res/drawable-anydpi/ic_add.xml | 11 + app/src/main/res/drawable-hdpi/ic_add.png | Bin 0 -> 148 bytes app/src/main/res/drawable-mdpi/ic_add.png | Bin 0 -> 101 bytes app/src/main/res/drawable-xhdpi/ic_add.png | Bin 0 -> 127 bytes app/src/main/res/drawable-xxhdpi/ic_add.png | Bin 0 -> 164 bytes .../main/res/layout/activity_file_view.xml | 3 +- app/src/main/res/layout/app_bar_main.xml | 1 - .../main/res/layout/dialog_content_scope.xml | 94 ++++ .../main/res/layout/dialog_customize_tags.xml | 106 ++++ .../main/res/layout/fragment_all_content.xml | 11 + .../res/layout/fragment_channel_content.xml | 11 + .../main/res/layout/fragment_following.xml | 12 +- .../layout/list_item_suggested_channel.xml | 1 + app/src/main/res/layout/list_item_tag.xml | 18 +- .../main/res/menu/activity_main_drawer.xml | 20 - app/src/main/res/menu/main.xml | 9 - app/src/main/res/values-night/colors.xml | 5 +- app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 26 +- app/src/main/res/xml/settings.xml | 7 + 41 files changed, 1761 insertions(+), 169 deletions(-) create mode 100644 app/src/main/java/io/lbry/browser/dialog/ContentScopeDialogFragment.java create mode 100644 app/src/main/java/io/lbry/browser/dialog/CustomizeTagsDialogFragment.java create mode 100644 app/src/main/java/io/lbry/browser/model/ClaimSearchCacheValue.java create mode 100644 app/src/main/java/io/lbry/browser/tasks/LoadTagsTask.java create mode 100644 app/src/main/java/io/lbry/browser/utils/Predefined.java create mode 100644 app/src/main/res/drawable-anydpi/ic_add.xml create mode 100644 app/src/main/res/drawable-hdpi/ic_add.png create mode 100644 app/src/main/res/drawable-mdpi/ic_add.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_add.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_add.png create mode 100644 app/src/main/res/layout/dialog_content_scope.xml create mode 100644 app/src/main/res/layout/dialog_customize_tags.xml delete mode 100644 app/src/main/res/menu/activity_main_drawer.xml delete mode 100644 app/src/main/res/menu/main.xml diff --git a/app/src/main/java/io/lbry/browser/FileViewActivity.java b/app/src/main/java/io/lbry/browser/FileViewActivity.java index 29cef886..fcd2ab2b 100644 --- a/app/src/main/java/io/lbry/browser/FileViewActivity.java +++ b/app/src/main/java/io/lbry/browser/FileViewActivity.java @@ -5,6 +5,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.content.res.Configuration; import android.net.Uri; import android.os.AsyncTask; @@ -18,6 +19,7 @@ import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import androidx.core.widget.NestedScrollView; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -292,11 +294,13 @@ public class FileViewActivity extends AppCompatActivity { TagListAdapter tagListAdapter = new TagListAdapter(tags, this); tagListAdapter.setClickListener(new TagListAdapter.TagClickListener() { @Override - public void onTagClicked(Tag tag) { - Intent intent = new Intent(MainActivity.ACTION_OPEN_ALL_CONTENT_TAG); - intent.putExtra("tag", tag.getName()); - sendBroadcast(intent); - moveTaskToBack(true); + public void onTagClicked(Tag tag, int customizeMode) { + if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_NONE) { + Intent intent = new Intent(MainActivity.ACTION_OPEN_ALL_CONTENT_TAG); + intent.putExtra("tag", tag.getName()); + sendBroadcast(intent); + moveTaskToBack(true); + } } }); descTagsList.setAdapter(tagListAdapter); @@ -378,7 +382,11 @@ public class FileViewActivity extends AppCompatActivity { String title = claim.getTitle(); String claimId = claim.getClaimId(); ProgressBar relatedLoading = findViewById(R.id.file_view_related_content_progress); - LighthouseSearchTask relatedTask = new LighthouseSearchTask(title, RELATED_CONTENT_SIZE, 0, false, claimId, relatedLoading, new ClaimSearchTask.ClaimSearchResultHandler() { + + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + LighthouseSearchTask relatedTask = new LighthouseSearchTask( + title, RELATED_CONTENT_SIZE, 0, canShowMatureContent, claimId, relatedLoading, new ClaimSearchTask.ClaimSearchResultHandler() { @Override public void onSuccess(List claims, boolean hasReachedEnd) { List filteredClaims = new ArrayList<>(); diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index babf6108..73edc5da 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -12,6 +12,7 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.TypedArray; +import android.database.sqlite.SQLiteDatabase; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; @@ -20,7 +21,6 @@ import android.os.Handler; import android.text.Editable; import android.text.TextWatcher; import android.util.Base64; -import android.util.Log; import android.view.View; import android.view.Menu; import android.view.inputmethod.InputMethodManager; @@ -63,6 +63,7 @@ import java.io.InputStreamReader; import java.net.ConnectException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -73,12 +74,13 @@ import java.util.concurrent.TimeUnit; import io.lbry.browser.adapter.NavigationMenuAdapter; import io.lbry.browser.adapter.UrlSuggestionListAdapter; import io.lbry.browser.data.DatabaseHelper; -import io.lbry.browser.exceptions.ApiCallException; +import io.lbry.browser.exceptions.LbryUriException; import io.lbry.browser.listener.SdkStatusListener; import io.lbry.browser.listener.WalletBalanceListener; import io.lbry.browser.model.Claim; import io.lbry.browser.model.ClaimCacheKey; import io.lbry.browser.model.NavMenuItem; +import io.lbry.browser.model.Tag; import io.lbry.browser.model.UrlSuggestion; import io.lbry.browser.model.WalletBalance; import io.lbry.browser.model.WalletSync; @@ -87,7 +89,6 @@ import io.lbry.browser.tasks.LighthouseAutoCompleteTask; import io.lbry.browser.tasks.ResolveTask; import io.lbry.browser.tasks.wallet.DefaultSyncTaskHandler; import io.lbry.browser.tasks.wallet.SyncGetTask; -import io.lbry.browser.tasks.wallet.SyncTaskHandler; import io.lbry.browser.tasks.wallet.WalletBalanceTask; import io.lbry.browser.ui.BaseFragment; import io.lbry.browser.ui.channel.ChannelFragment; @@ -103,6 +104,7 @@ import io.lbry.browser.utils.Lbryio; import io.lbry.lbrysdk.LbrynetService; import io.lbry.lbrysdk.ServiceHelper; import io.lbry.lbrysdk.Utils; +import lombok.Data; import lombok.Getter; public class MainActivity extends AppCompatActivity implements SdkStatusListener { @@ -141,6 +143,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener // preference keys public static final String PREFERENCE_KEY_DARK_MODE = "io.lbry.browser.preference.userinterface.DarkMode"; + public static final String PREFERENCE_KEY_SHOW_MATURE_CONTENT = "io.lbry.browser.preference.userinterface.ShowMatureContent"; public static final String PREFERENCE_KEY_NOTIFICATION_URL_SUGGESTIONS = "io.lbry.browser.preference.userinterface.UrlSuggestions"; public static final String PREFERENCE_KEY_NOTIFICATION_SUBSCRIPTIONS = "io.lbry.browser.preference.notifications.Subscriptions"; public static final String PREFERENCE_KEY_NOTIFICATION_REWARDS = "io.lbry.browser.preference.notifications.Rewards"; @@ -164,6 +167,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private NavigationMenuAdapter navMenuAdapter; private UrlSuggestionListAdapter urlSuggestionListAdapter; + private List recentHistory; // broadcast receivers private BroadcastReceiver serviceActionsReceiver; @@ -526,10 +530,18 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private void toggleUrlSuggestions(boolean visible) { View container = findViewById(R.id.url_suggestions_container); View closeIcon = findViewById(R.id.wunderbar_close); + EditText wunderbar = findViewById(R.id.wunderbar); + wunderbar.setPadding(0, 0, visible ? getScaledValue(36) : 0, 0); + container.setVisibility(visible ? View.VISIBLE : View.GONE); closeIcon.setVisibility(visible ? View.VISIBLE : View.GONE); } + private int getScaledValue(int value) { + float scale = getResources().getDisplayMetrics().density; + return (int) (value * scale + 0.5f); + } + private void setupUriBar() { findViewById(R.id.wunderbar_close).setOnClickListener(new View.OnClickListener() { @Override @@ -543,6 +555,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener @Override public void onFocusChange(View view, boolean hasFocus) { toggleUrlSuggestions(hasFocus); + if (hasFocus && Helper.isNullOrEmpty(Helper.getValue(((EditText) view).getText()))) { + displayUrlSuggestionsForNoInput(); + } } }); @@ -572,6 +587,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener switch (urlSuggestion.getType()) { case UrlSuggestion.TYPE_CHANNEL: // open channel page + openChannelUrl(urlSuggestion.getUri().toString()); break; case UrlSuggestion.TYPE_FILE: Context context = MainActivity.this; @@ -586,6 +602,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener break; case UrlSuggestion.TYPE_TAG: // open tag page + openAllContentFragmentWithTag(urlSuggestion.getText()); break; } findViewById(R.id.wunderbar).clearFocus(); @@ -618,24 +635,26 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener ResolveTask task = new ResolveTask(urls, Lbry.LBRY_TV_CONNECTION_STRING, null, new ResolveTask.ResolveResultHandler() { @Override public void onSuccess(List claims) { - for (int i = 0; i < claims.size(); i++) { - // build a simple url from the claim for matching - Claim claim = claims.get(i); - if (Helper.isNullOrEmpty(claim.getName())) { - continue; - } + if (findViewById(R.id.url_suggestions_container).getVisibility() == View.VISIBLE) { + for (int i = 0; i < claims.size(); i++) { + // build a simple url from the claim for matching + Claim claim = claims.get(i); + if (Helper.isNullOrEmpty(claim.getName())) { + continue; + } - LbryUri simpleUrl = new LbryUri(); - if (claim.getName().startsWith("@")) { - // channel - simpleUrl.setChannelName(claim.getName()); - } else { - simpleUrl.setStreamName(claim.getName()); - } + LbryUri simpleUrl = new LbryUri(); + if (claim.getName().startsWith("@")) { + // channel + simpleUrl.setChannelName(claim.getName()); + } else { + simpleUrl.setStreamName(claim.getName()); + } - urlSuggestionListAdapter.setClaimForUrl(simpleUrl, claim); + urlSuggestionListAdapter.setClaimForUrl(simpleUrl, claim); + } + urlSuggestionListAdapter.notifyDataSetChanged(); } - urlSuggestionListAdapter.notifyDataSetChanged(); } @Override @@ -646,10 +665,19 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); } + private void displayUrlSuggestionsForNoInput() { + urlSuggestionListAdapter.clear(); + List blankSuggestions = buildDefaultSuggestionsForBlankUrl(); + urlSuggestionListAdapter.addUrlSuggestions(blankSuggestions); + List urls = urlSuggestionListAdapter.getItemUrls(); + resolveUrlSuggestions(urls); + } + private void handleUriInputChanged(String text) { // build the default suggestions urlSuggestionListAdapter.clear(); - if (Helper.isNullOrEmpty(text)) { + if (Helper.isNullOrEmpty(text) || text.trim().equals("@")) { + displayUrlSuggestionsForNoInput(); return; } @@ -659,10 +687,12 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener LighthouseAutoCompleteTask task = new LighthouseAutoCompleteTask(text, null, new LighthouseAutoCompleteTask.AutoCompleteResultHandler() { @Override public void onSuccess(List suggestions) { - urlSuggestionListAdapter.addUrlSuggestions(suggestions); - - List urls = urlSuggestionListAdapter.getItemUrls(); - resolveUrlSuggestions(urls); + String wunderBarText = Helper.getValue(((EditText) findViewById(R.id.wunderbar)).getText()); + if (wunderBarText.equalsIgnoreCase(text)) { + urlSuggestionListAdapter.addUrlSuggestions(suggestions); + List urls = urlSuggestionListAdapter.getItemUrls(); + resolveUrlSuggestions(urls); + } } @Override @@ -673,12 +703,37 @@ 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"))); + for (UrlSuggestion suggestion : suggestions) { + suggestion.setUseTextAsDescription(true); + } + } catch (LbryUriException ex) { + // pass + } + } + return suggestions; + } + private List buildDefaultSuggestions(String text) { List suggestions = new ArrayList(); // First item is always search - UrlSuggestion searchSuggestion = new UrlSuggestion(UrlSuggestion.TYPE_SEARCH, text); - suggestions.add(searchSuggestion); + if (!text.startsWith(LbryUri.PROTO_DEFAULT)) { + UrlSuggestion searchSuggestion = new UrlSuggestion(UrlSuggestion.TYPE_SEARCH, text); + suggestions.add(searchSuggestion); + } if (!text.matches(LbryUri.REGEX_INVALID_URI)) { boolean isChannel = text.startsWith("@"); @@ -990,6 +1045,12 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener return false; } + SQLiteDatabase db = dbHelper.getReadableDatabase(); + List fetchedTags = DatabaseHelper.getTags(db); + Lbry.knownTags = Helper.mergeKnownTags(fetchedTags); + Collections.sort(Lbry.knownTags, new Tag()); + Lbry.followedTags = Helper.filterFollowedTags(Lbry.knownTags); + // load the exchange rate if (Lbryio.LBCUSDRate == 0) { Lbryio.loadExchangeRate(); @@ -1175,7 +1236,6 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener findContentGroup.setItems(Arrays.asList( new NavMenuItem(NavMenuItem.ID_ITEM_FOLLOWING, R.string.fa_heart, R.string.following, "Following", context), new NavMenuItem(NavMenuItem.ID_ITEM_EDITORS_CHOICE, R.string.fa_star, R.string.editors_choice, "EditorsChoice", context), - new NavMenuItem(NavMenuItem.ID_ITEM_YOUR_TAGS, R.string.fa_hashtag, R.string.your_tags, "YourTags", context), new NavMenuItem(NavMenuItem.ID_ITEM_ALL_CONTENT, R.string.fa_globe_americas, R.string.all_content, "AllContent", context) )); @@ -1421,4 +1481,8 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener }); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + + private void loadTags() { + + } } diff --git a/app/src/main/java/io/lbry/browser/adapter/TagListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/TagListAdapter.java index 5a7a4faa..3ed25b93 100644 --- a/app/src/main/java/io/lbry/browser/adapter/TagListAdapter.java +++ b/app/src/main/java/io/lbry/browser/adapter/TagListAdapter.java @@ -4,6 +4,7 @@ import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; @@ -13,23 +14,35 @@ import java.util.List; import io.lbry.browser.R; import io.lbry.browser.model.Tag; +import lombok.Getter; import lombok.Setter; public class TagListAdapter extends RecyclerView.Adapter { + + public static final int CUSTOMIZE_MODE_NONE = 0; + public static final int CUSTOMIZE_MODE_ADD = 1; + public static final int CUSTOMIZE_MODE_REMOVE = 2; + private Context context; private List items; @Setter private TagClickListener clickListener; + @Getter + @Setter + private int customizeMode; public TagListAdapter(List tags, Context context) { this.context = context; this.items = new ArrayList<>(tags); + this.customizeMode = CUSTOMIZE_MODE_NONE; } public static class ViewHolder extends RecyclerView.ViewHolder { + protected ImageView iconView; protected TextView nameView; public ViewHolder(View v) { super(v); + iconView = v.findViewById(R.id.tag_action); nameView = v.findViewById(R.id.tag_name); } } @@ -38,6 +51,21 @@ public class TagListAdapter extends RecyclerView.Adapter getTags() { + return new ArrayList<>(items); + } + + public void setTags(List tags) { + items = new ArrayList<>(tags); + notifyDataSetChanged(); + } + public void addTags(List tags) { for (Tag tag : tags) { if (!items.contains(tag)) { @@ -46,6 +74,10 @@ public class TagListAdapter extends RecyclerView.Adapter getRecentHistory(SQLiteDatabase db) { + List suggestions = new ArrayList<>(); + Cursor cursor = null; + try { + cursor = db.rawQuery(SQL_GET_RECENT_HISTORY, null); + while (cursor.moveToNext()) { + UrlSuggestion suggestion = new UrlSuggestion(); + suggestion.setText(cursor.getString(0)); + suggestion.setType(cursor.getInt(2)); + + try { + suggestion.setUri(cursor.isNull(1) ? null : LbryUri.parse(cursor.getString(1))); + } catch (LbryUriException ex) { + // don't fail if the LbryUri is invalid + } + suggestions.add(suggestion); + } + } finally { + Helper.closeCursor(cursor); + } + return suggestions; + } + + public static void createOrUpdateTag(Tag tag, SQLiteDatabase db) { + db.execSQL(SQL_INSERT_TAG, new Object[] { tag.getLowercaseName(), tag.isFollowed() ? 1 : 0 }); + } + public static void setTagFollowed(boolean followed, String name, SQLiteDatabase db) { + db.execSQL(SQL_SET_TAG_FOLLOWED, new Object[] { followed ? 1 : 0, name }); + } + public static List getTags(SQLiteDatabase db) { + List tags = new ArrayList<>(); + Cursor cursor = null; + try { + cursor = db.rawQuery(SQL_GET_KNOWN_TAGS, null); + while (cursor.moveToNext()) { + Tag tag = new Tag(); + tag.setName(cursor.getString(0)); + tag.setFollowed(cursor.getInt(1) == 1); + tags.add(tag); + } + } finally { + Helper.closeCursor(cursor); + } + return tags; + } + public static void createOrUpdateSubscription(Subscription subscription, SQLiteDatabase db) { db.execSQL(SQL_INSERT_SUBSCRIPTION, new Object[] { subscription.getChannelName(), subscription.getUrl() }); } diff --git a/app/src/main/java/io/lbry/browser/dialog/ContentScopeDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/ContentScopeDialogFragment.java new file mode 100644 index 00000000..4db24f8d --- /dev/null +++ b/app/src/main/java/io/lbry/browser/dialog/ContentScopeDialogFragment.java @@ -0,0 +1,96 @@ +package io.lbry.browser.dialog; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import io.lbry.browser.R; +import lombok.Setter; + +public class ContentScopeDialogFragment extends BottomSheetDialogFragment { + public static final String TAG = "ContentScopeDialog"; + public static final int ITEM_EVERYONE = 1; + public static final int ITEM_TAGS = 2; + + @Setter + private ContentScopeListener contentScopeListener; + private int currentScopeItem; + + public static ContentScopeDialogFragment newInstance() { + return new ContentScopeDialogFragment(); + } + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_content_scope, container,false); + + ContentScopeItemClickListener clickListener = new ContentScopeItemClickListener(this, contentScopeListener); + view.findViewById(R.id.content_scope_everyone_item).setOnClickListener(clickListener); + view.findViewById(R.id.content_scope_tags_item).setOnClickListener(clickListener); + checkSelectedScopeItem(currentScopeItem, view); + + return view; + } + + public static void checkSelectedScopeItem(int scope, View parent) { + int checkViewId = -1; + switch (scope) { + case ITEM_EVERYONE: checkViewId = R.id.content_scope_everyone_item_selected; break; + case ITEM_TAGS: checkViewId = R.id.content_scope_tags_item_selected; break; + } + if (parent != null && checkViewId > -1) { + parent.findViewById(checkViewId).setVisibility(View.VISIBLE); + } + } + + public void setCurrentScopeItem(int scopeItem) { + this.currentScopeItem = scopeItem; + } + + private static class ContentScopeItemClickListener implements View.OnClickListener { + + private final int[] checkViewIds = { + R.id.content_scope_everyone_item_selected, R.id.content_scope_tags_item_selected + }; + private BottomSheetDialogFragment dialog; + private ContentScopeListener listener; + + public ContentScopeItemClickListener(BottomSheetDialogFragment dialog, ContentScopeListener listener) { + this.dialog = dialog; + this.listener = listener; + } + + public void onClick(View view) { + int scopeItem = -1; + + if (dialog != null) { + View dialogView = dialog.getView(); + if (dialogView != null) { + for (int id : checkViewIds) { + dialogView.findViewById(id).setVisibility(View.GONE); + } + } + } + + switch (view.getId()) { + case R.id.content_scope_everyone_item: scopeItem = ITEM_EVERYONE; break; + case R.id.content_scope_tags_item: scopeItem = ITEM_TAGS; break; + } + + checkSelectedScopeItem(scopeItem, view); + if (listener != null) { + listener.onContentScopeItemSelected(scopeItem); + } + + if (dialog != null) { + dialog.dismiss(); + } + } + } + + public interface ContentScopeListener { + void onContentScopeItemSelected(int scopeItem); + } +} diff --git a/app/src/main/java/io/lbry/browser/dialog/CustomizeTagsDialogFragment.java b/app/src/main/java/io/lbry/browser/dialog/CustomizeTagsDialogFragment.java new file mode 100644 index 00000000..c7ef5815 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/dialog/CustomizeTagsDialogFragment.java @@ -0,0 +1,211 @@ +package io.lbry.browser.dialog; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.flexbox.FlexboxLayoutManager; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.textfield.TextInputEditText; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import io.lbry.browser.R; +import io.lbry.browser.adapter.TagListAdapter; +import io.lbry.browser.model.Tag; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import lombok.Setter; + +public class CustomizeTagsDialogFragment extends BottomSheetDialogFragment { + public static final String TAG = "CustomizeTagsDialog"; + private static final int SUGGESTED_LIMIT = 8; + private String currentFilter; + + private RecyclerView followedTagsList; + private RecyclerView suggestedTagsList; + private TagListAdapter followedTagsAdapter; + private TagListAdapter suggestedTagsAdapter; + private View noTagsView; + private View noResultsView; + @Setter + private TagListener listener; + + private void checkNoTags() { + Helper.setViewVisibility(noTagsView, followedTagsAdapter == null || followedTagsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + private void checkNoResults() { + Helper.setViewVisibility(noResultsView, suggestedTagsAdapter == null || suggestedTagsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + public void addTag(Tag tag) { + if (followedTagsAdapter.getTags().contains(tag)) { + Snackbar.make(getView(), getString(R.string.tag_already_followed, tag.getName()), Snackbar.LENGTH_LONG).show(); + return; + } + + tag.setFollowed(true); + followedTagsAdapter.addTag(tag); + if (suggestedTagsAdapter != null) { + suggestedTagsAdapter.removeTag(tag); + } + updateKnownTags(currentFilter, SUGGESTED_LIMIT, false); + if (listener != null) { + listener.onTagAdded(tag); + } + checkNoTags(); + checkNoResults(); + } + public void removeTag(Tag tag) { + tag.setFollowed(false); + followedTagsAdapter.removeTag(tag); + updateKnownTags(currentFilter, SUGGESTED_LIMIT, false); + if (listener != null) { + listener.onTagRemoved(tag); + } + checkNoTags(); + checkNoResults(); + } + + public void setFilter(String filter) { + currentFilter = filter; + updateKnownTags(currentFilter, SUGGESTED_LIMIT, true); + } + + public static CustomizeTagsDialogFragment newInstance() { + return new CustomizeTagsDialogFragment(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_customize_tags, container, false); + + noResultsView = view.findViewById(R.id.customize_no_tag_results); + noTagsView = view.findViewById(R.id.customize_no_followed_tags); + + followedTagsAdapter = new TagListAdapter(Lbry.followedTags, getContext()); + followedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_REMOVE); + followedTagsAdapter.setClickListener(customizeTagClickListener); + suggestedTagsAdapter = new TagListAdapter(new ArrayList<>(), getContext()); + suggestedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD); + suggestedTagsAdapter.setClickListener(customizeTagClickListener); + + FlexboxLayoutManager flm1 = new FlexboxLayoutManager(getContext()); + followedTagsList = view.findViewById(R.id.customize_tags_followed_list); + followedTagsList.setLayoutManager(flm1); + followedTagsList.setAdapter(followedTagsAdapter); + + FlexboxLayoutManager flm2 = new FlexboxLayoutManager(getContext()); + suggestedTagsList = view.findViewById(R.id.customize_tags_suggested_list); + suggestedTagsList.setLayoutManager(flm2); + suggestedTagsList.setAdapter(suggestedTagsAdapter); + + TextInputEditText filterInput = view.findViewById(R.id.customize_tag_filter_input); + filterInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + String value = Helper.getValue(charSequence); + setFilter(value); + } + + @Override + public void afterTextChanged(Editable editable) { + + } + }); + + + MaterialButton doneButton = view.findViewById(R.id.customize_done_button); + doneButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + checkNoTags(); + return view; + } + + private TagListAdapter.TagClickListener customizeTagClickListener = new TagListAdapter.TagClickListener() { + @Override + public void onTagClicked(Tag tag, int customizeMode) { + if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_ADD) { + addTag(tag); + } else if (customizeMode == TagListAdapter.CUSTOMIZE_MODE_REMOVE) { + removeTag(tag); + } + } + }; + + public void onResume() { + super.onResume(); + updateKnownTags(null, SUGGESTED_LIMIT, true); + } + + private void updateKnownTags(String filter, int limit, boolean clearPrevious) { + (new AsyncTask>() { + protected List doInBackground(Void... params) { + List tags = new ArrayList<>(); + if (Helper.isNullOrEmpty(filter)) { + Random random = new Random(); + if (suggestedTagsAdapter != null && !clearPrevious) { + tags = new ArrayList<>(suggestedTagsAdapter.getTags()); + } + while (tags.size() < limit) { + Tag randomTag = Lbry.knownTags.get(random.nextInt(Lbry.knownTags.size())); + if (!Lbry.followedTags.contains(randomTag) && (followedTagsAdapter == null || !followedTagsAdapter.getTags().contains(randomTag))) { + tags.add(randomTag); + } + } + } else { + Tag filterTag = new Tag(filter); + if (followedTagsAdapter == null || !followedTagsAdapter.getTags().contains(filterTag)) { + tags.add(new Tag(filter)); + } + for (int i = 0; i < Lbry.knownTags.size() && tags.size() < SUGGESTED_LIMIT - 1; i++) { + Tag knownTag = Lbry.knownTags.get(i); + if ((knownTag.getLowercaseName().startsWith(filter) || knownTag.getLowercaseName().matches(filter)) && + (!tags.contains(knownTag) && + !Lbry.followedTags.contains(knownTag) && (followedTagsAdapter == null || !followedTagsAdapter.getTags().contains(knownTag)))) { + tags.add(knownTag); + } + } + } + return tags; + } + protected void onPostExecute(List tags) { + if (suggestedTagsAdapter == null) { + suggestedTagsAdapter = new TagListAdapter(tags, getContext()); + suggestedTagsAdapter.setCustomizeMode(TagListAdapter.CUSTOMIZE_MODE_ADD); + suggestedTagsAdapter.setClickListener(customizeTagClickListener); + if (suggestedTagsList != null) { + suggestedTagsList.setAdapter(suggestedTagsAdapter); + } + } else { + suggestedTagsAdapter.setTags(tags); + } + checkNoResults(); + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public interface TagListener { + void onTagAdded(Tag tag); + void onTagRemoved(Tag tag); + } +} diff --git a/app/src/main/java/io/lbry/browser/model/ClaimSearchCacheValue.java b/app/src/main/java/io/lbry/browser/model/ClaimSearchCacheValue.java new file mode 100644 index 00000000..60260add --- /dev/null +++ b/app/src/main/java/io/lbry/browser/model/ClaimSearchCacheValue.java @@ -0,0 +1,26 @@ +package io.lbry.browser.model; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +public class ClaimSearchCacheValue { + @Getter + @Setter + private List claims; + @Getter + @Setter + private long timestamp; + + public ClaimSearchCacheValue(List claims, long timestamp) { + this.claims = new ArrayList<>(claims); + this.timestamp = timestamp; + } + + public boolean isExpired(long ttl) { + return System.currentTimeMillis() - timestamp > ttl; + } +} diff --git a/app/src/main/java/io/lbry/browser/model/NavMenuItem.java b/app/src/main/java/io/lbry/browser/model/NavMenuItem.java index 02e4dd14..67e1b8a7 100644 --- a/app/src/main/java/io/lbry/browser/model/NavMenuItem.java +++ b/app/src/main/java/io/lbry/browser/model/NavMenuItem.java @@ -19,8 +19,7 @@ public class NavMenuItem { // Find Content public static final int ID_ITEM_FOLLOWING = 101; public static final int ID_ITEM_EDITORS_CHOICE = 102; - public static final int ID_ITEM_YOUR_TAGS = 103; - public static final int ID_ITEM_ALL_CONTENT = 104; + public static final int ID_ITEM_ALL_CONTENT = 103; // Your Content public static final int ID_ITEM_CHANNELS = 201; diff --git a/app/src/main/java/io/lbry/browser/model/Tag.java b/app/src/main/java/io/lbry/browser/model/Tag.java index c89d3691..40b57ac0 100644 --- a/app/src/main/java/io/lbry/browser/model/Tag.java +++ b/app/src/main/java/io/lbry/browser/model/Tag.java @@ -1,14 +1,22 @@ package io.lbry.browser.model; -import io.lbry.browser.utils.Helper; +import java.util.Comparator; + +import io.lbry.browser.utils.Predefined; import lombok.Getter; import lombok.Setter; -public class Tag { +public class Tag implements Comparator { @Getter @Setter private String name; + @Getter + @Setter + private boolean followed; + public Tag() { + + } public Tag(String name) { this.name = name; } @@ -18,13 +26,16 @@ public class Tag { } public boolean isMature() { - return Helper.MATURE_TAG_NAMES.contains(name.toLowerCase()); + return Predefined.MATURE_TAGS.contains(name.toLowerCase()); } public boolean equals(Object o) { return (o instanceof Tag) && ((Tag) o).getName().equalsIgnoreCase(name); } public int hashCode() { - return name.hashCode(); + return name.toLowerCase().hashCode(); + } + public int compare(Tag a, Tag b) { + return a.getLowercaseName().compareToIgnoreCase(b.getLowercaseName()); } } diff --git a/app/src/main/java/io/lbry/browser/model/UrlSuggestion.java b/app/src/main/java/io/lbry/browser/model/UrlSuggestion.java index f0685329..f7a6234b 100644 --- a/app/src/main/java/io/lbry/browser/model/UrlSuggestion.java +++ b/app/src/main/java/io/lbry/browser/model/UrlSuggestion.java @@ -14,20 +14,35 @@ public class UrlSuggestion { private String text; private LbryUri uri; private Claim claim; // associated claim if resolved + private boolean titleTextOnly; + private boolean useTextAsDescription; + public UrlSuggestion() { + + } public UrlSuggestion(int type, String text) { this.type = type; this.text = text; } + public UrlSuggestion(int type, String text, LbryUri uri) { + this(type, text); + this.uri = uri; + } + public UrlSuggestion(int type, String text, LbryUri uri, boolean titleTextOnly) { + this(type, text, uri); + this.titleTextOnly = titleTextOnly; + } public String getTitle() { - switch (type) { - case TYPE_CHANNEL: - return String.format("%s - %s", text.startsWith("@") ? text.substring(1) : text, uri.toString()); - case TYPE_FILE: - return String.format("%s - %s", text, uri.toString()); - case TYPE_TAG: - return String.format("%s - #%s", text, text); + if (!titleTextOnly) { + switch (type) { + case TYPE_CHANNEL: + return String.format("%s - %s", text.startsWith("@") ? text.substring(1) : text, uri.toVanityString()); + case TYPE_FILE: + return String.format("%s - %s", text, uri.toVanityString()); + case TYPE_TAG: + return String.format("%s - #%s", text, text); + } } return text; diff --git a/app/src/main/java/io/lbry/browser/tasks/LoadTagsTask.java b/app/src/main/java/io/lbry/browser/tasks/LoadTagsTask.java new file mode 100644 index 00000000..4eb465e3 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/tasks/LoadTagsTask.java @@ -0,0 +1,59 @@ +package io.lbry.browser.tasks; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.os.AsyncTask; +import android.view.View; +import android.widget.ProgressBar; + +import java.util.List; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.data.DatabaseHelper; +import io.lbry.browser.exceptions.LbryRequestException; +import io.lbry.browser.exceptions.LbryResponseException; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.Tag; +import io.lbry.browser.utils.Helper; + +public class LoadTagsTask extends AsyncTask> { + private Context context; + private LoadTagsHandler handler; + private Exception error; + + public LoadTagsTask(Context context, LoadTagsHandler handler) { + this.context = context; + this.handler = handler; + } + protected List doInBackground(Void... params) { + List tags = null; + SQLiteDatabase db = null; + try { + if (context instanceof MainActivity) { + db = ((MainActivity) context).getDbHelper().getReadableDatabase(); + if (db != null) { + tags = DatabaseHelper.getTags(db); + } + } + } catch (SQLiteException ex) { + error = ex; + } + + return tags; + } + protected void onPostExecute(List tags) { + if (handler != null) { + if (tags != null) { + handler.onSuccess(tags); + } else { + handler.onError(error); + } + } + } + + public interface LoadTagsHandler { + void onSuccess(List tags); + void onError(Exception error); + } +} diff --git a/app/src/main/java/io/lbry/browser/ui/allcontent/AllContentFragment.java b/app/src/main/java/io/lbry/browser/ui/allcontent/AllContentFragment.java index df7a79de..c1795d24 100644 --- a/app/src/main/java/io/lbry/browser/ui/allcontent/AllContentFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/allcontent/AllContentFragment.java @@ -2,6 +2,7 @@ package io.lbry.browser.ui.allcontent; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.view.LayoutInflater; @@ -10,9 +11,12 @@ import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.google.android.material.snackbar.Snackbar; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -23,19 +27,24 @@ import io.lbry.browser.MainActivity; import io.lbry.browser.R; import io.lbry.browser.adapter.ClaimListAdapter; 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.model.Claim; +import io.lbry.browser.model.Tag; import io.lbry.browser.tasks.ClaimSearchTask; import io.lbry.browser.ui.BaseFragment; import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.Predefined; // TODO: Similar code to FollowingFragment and Channel page fragment. Probably make common operations (sorting/filtering) into a control -public class AllContentFragment extends BaseFragment { +public class AllContentFragment extends BaseFragment implements SharedPreferences.OnSharedPreferenceChangeListener { private boolean singleTagView; private List tags; private View layoutFilterContainer; + private View customizeLink; private View sortLink; private View contentFromLink; private View scopeLink; @@ -46,13 +55,14 @@ public class AllContentFragment extends BaseFragment { private RecyclerView contentList; private int currentSortBy; private int currentContentFrom; - private int currentScope; + private int currentContentScope; private String contentReleaseTime; private List contentSortOrder; private View fromPrefix; private View forPrefix; private View contentLoading; private View bigContentLoading; + private View noContentView; private ClaimListAdapter contentListAdapter; private boolean contentClaimSearchLoading; private boolean contentHasReachedEnd; @@ -72,6 +82,7 @@ public class AllContentFragment extends BaseFragment { sortLink = root.findViewById(R.id.all_content_sort_link); contentFromLink = root.findViewById(R.id.all_content_time_link); scopeLink = root.findViewById(R.id.all_content_scope_link); + customizeLink = root.findViewById(R.id.all_content_customize_link); fromPrefix = root.findViewById(R.id.all_content_from_prefix); forPrefix = root.findViewById(R.id.all_content_for_prefix); @@ -81,6 +92,7 @@ public class AllContentFragment extends BaseFragment { bigContentLoading = root.findViewById(R.id.all_content_main_progress); contentLoading = root.findViewById(R.id.all_content_load_progress); + noContentView = root.findViewById(R.id.all_content_no_claim_search_content); contentList = root.findViewById(R.id.all_content_list); LinearLayoutManager llm = new LinearLayoutManager(getContext()); @@ -127,6 +139,25 @@ public class AllContentFragment extends BaseFragment { } } }); + scopeLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ContentScopeDialogFragment dialog = ContentScopeDialogFragment.newInstance(); + dialog.setCurrentScopeItem(currentContentScope); + dialog.setContentScopeListener(new ContentScopeDialogFragment.ContentScopeListener() { + @Override + public void onContentScopeItemSelected(int scopeItem) { + onContentScopeChanged(scopeItem); + } + }); + + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + dialog.show(activity.getSupportFragmentManager(), ContentScopeDialogFragment.TAG); + } + } + }); contentFromLink.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -145,6 +176,12 @@ public class AllContentFragment extends BaseFragment { } } }); + customizeLink.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showCustomizeTagsDialog(); + } + }); checkParams(false); @@ -188,6 +225,51 @@ public class AllContentFragment extends BaseFragment { fetchClaimSearchContent(true); } + private void onContentScopeChanged(int contentScope) { + currentContentScope = contentScope; + + // rebuild options and search + updateContentScopeLinkText(); + boolean isTagScope = currentContentScope == ContentScopeDialogFragment.ITEM_TAGS; + if (isTagScope) { + tags = Helper.getTagsForTagObjects(Lbry.followedTags); + // Update tags list with the user's followed tags + if (tags == null || tags.size() == 0) { + Snackbar.make(getView(), R.string.customize_tags_hint, Snackbar.LENGTH_LONG).setAction(R.string.customize, new View.OnClickListener() { + @Override + public void onClick(View view) { + // show customize + showCustomizeTagsDialog(); + } + }).show(); + } + } + Helper.setViewVisibility(customizeLink, isTagScope ? View.VISIBLE : View.GONE); + fetchClaimSearchContent(true); + } + + private void showCustomizeTagsDialog() { + CustomizeTagsDialogFragment dialog = CustomizeTagsDialogFragment.newInstance(); + dialog.setListener(new CustomizeTagsDialogFragment.TagListener() { + @Override + public void onTagAdded(Tag tag) { + // heavy-lifting + // save to local, save to wallet and then sync + } + + @Override + public void onTagRemoved(Tag tag) { + // heavy-lifting + // save to local, save to wallet and then sync + } + }); + Context context = getContext(); + if (context instanceof MainActivity) { + MainActivity activity = (MainActivity) context; + dialog.show(activity.getSupportFragmentManager(), CustomizeTagsDialogFragment.TAG); + } + } + private void onSortByChanged(int sortBy) { currentSortBy = sortBy; @@ -214,6 +296,16 @@ public class AllContentFragment extends BaseFragment { Helper.setViewText(sortLinkText, stringResourceId); } + private void updateContentScopeLinkText() { + int stringResourceId = -1; + switch (currentContentScope) { + case ContentScopeDialogFragment.ITEM_EVERYONE: default: stringResourceId = R.string.everyone; break; + case ContentScopeDialogFragment.ITEM_TAGS: stringResourceId = R.string.tags; break; + } + + Helper.setViewText(scopeLinkText, stringResourceId); + } + private void updateContentFromLinkText() { int stringResourceId = -1; switch (currentContentFrom) { @@ -229,14 +321,26 @@ public class AllContentFragment extends BaseFragment { public void onResume() { super.onResume(); + PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this); + updateContentFromLinkText(); + updateContentScopeLinkText(); + updateSortByLinkText(); fetchClaimSearchContent(); } + public void onPause() { + PreferenceManager.getDefaultSharedPreferences(getContext()).unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); + } + private Map buildContentOptions() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + return Lbry.buildClaimSearchOptions( Claim.TYPE_STREAM, - tags != null ? tags : null, - null, // TODO: Check mature + (currentContentScope == ContentScopeDialogFragment.ITEM_EVERYONE) ? null : tags, + canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS), null, null, getContentSortOrder(), @@ -267,6 +371,7 @@ public class AllContentFragment extends BaseFragment { } contentClaimSearchLoading = true; + Helper.setViewVisibility(noContentView, View.GONE); Map claimSearchOptions = buildContentOptions(); contentClaimSearchTask = new ClaimSearchTask(claimSearchOptions, Lbry.LBRY_TV_CONNECTION_STRING, getLoadingView(), new ClaimSearchTask.ClaimSearchResultHandler() { @Override @@ -303,13 +408,26 @@ public class AllContentFragment extends BaseFragment { contentHasReachedEnd = hasReachedEnd; contentClaimSearchLoading = false; + checkNoContent(); } @Override public void onError(Exception error) { contentClaimSearchLoading = false; + checkNoContent(); } }); contentClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + + private void checkNoContent() { + boolean noContent = contentListAdapter == null || contentListAdapter.getItemCount() == 0; + Helper.setViewVisibility(noContentView, noContent ? View.VISIBLE : View.GONE); + } + + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (key.equalsIgnoreCase(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT)) { + fetchClaimSearchContent(true); + } + } } diff --git a/app/src/main/java/io/lbry/browser/ui/channel/ChannelContentFragment.java b/app/src/main/java/io/lbry/browser/ui/channel/ChannelContentFragment.java index 02b03280..30cdb2a8 100644 --- a/app/src/main/java/io/lbry/browser/ui/channel/ChannelContentFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/channel/ChannelContentFragment.java @@ -2,6 +2,7 @@ package io.lbry.browser.ui.channel; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.view.LayoutInflater; @@ -11,9 +12,11 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -28,9 +31,10 @@ import io.lbry.browser.model.Claim; import io.lbry.browser.tasks.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 { +public class ChannelContentFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener { @Setter private String channelId; @@ -45,6 +49,7 @@ public class ChannelContentFragment extends Fragment { private List contentSortOrder; private View contentLoading; private View bigContentLoading; + private View noContentView; private ClaimListAdapter contentListAdapter; private boolean contentClaimSearchLoading; private boolean contentHasReachedEnd; @@ -66,6 +71,7 @@ public class ChannelContentFragment extends Fragment { bigContentLoading = root.findViewById(R.id.channel_content_main_progress); contentLoading = root.findViewById(R.id.channel_content_load_progress); + noContentView = root.findViewById(R.id.channel_content_no_claim_search_content); contentList = root.findViewById(R.id.channel_content_list); LinearLayoutManager llm = new LinearLayoutManager(getContext()); @@ -183,18 +189,23 @@ public class ChannelContentFragment extends Fragment { public void onResume() { super.onResume(); + PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this); fetchClaimSearchContent(); } - public void refresh() { - fetchClaimSearchContent(true); + public void onPause() { + PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this); + super.onPause(); } private Map buildContentOptions() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + return Lbry.buildClaimSearchOptions( Claim.TYPE_STREAM, null, - null, // TODO: Check mature + canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS), Arrays.asList(channelId), null, getContentSortOrder(), @@ -225,6 +236,7 @@ public class ChannelContentFragment extends Fragment { } contentClaimSearchLoading = true; + Helper.setViewVisibility(noContentView, View.GONE); Map claimSearchOptions = buildContentOptions(); contentClaimSearchTask = new ClaimSearchTask(claimSearchOptions, Lbry.LBRY_TV_CONNECTION_STRING, getLoadingView(), new ClaimSearchTask.ClaimSearchResultHandler() { @Override @@ -261,13 +273,26 @@ public class ChannelContentFragment extends Fragment { contentHasReachedEnd = hasReachedEnd; contentClaimSearchLoading = false; + checkNoContent(); } @Override public void onError(Exception error) { contentClaimSearchLoading = false; + checkNoContent(); } }); contentClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + + private void checkNoContent() { + boolean noContent = contentListAdapter == null || contentListAdapter.getItemCount() == 0; + Helper.setViewVisibility(noContentView, noContent ? View.VISIBLE : View.GONE); + } + + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (key.equalsIgnoreCase(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT)) { + fetchClaimSearchContent(true); + } + } } 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 9d9d7636..be9ffb58 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 @@ -96,7 +96,6 @@ public class ChannelFragment extends BaseFragment { } } if (updateRequired) { - resetFragments(); if (!Helper.isNullOrEmpty(url)) { resolveUrl(); } else if (claim == null) { @@ -179,25 +178,6 @@ public class ChannelFragment extends BaseFragment { }).attach(); } - private void resetFragments() { - try { - Context context = getContext(); - if (context instanceof MainActivity) { - MainActivity activity = (MainActivity) getContext(); - FragmentManager manager = activity.getSupportFragmentManager(); - FragmentTransaction tx = manager.beginTransaction(); - for (Fragment fragment : manager.getFragments()) { - if (fragment.getClass().equals(ChannelAboutFragment.class) || fragment.getClass().equals(ChannelContentFragment.class)) { - tx.remove(fragment); - } - } - tx.commitAllowingStateLoss(); - } - } catch (Exception ex) { - // pass - } - } - private static class ChannelPagerAdapter extends FragmentStateAdapter { private Claim channelClaim; public ChannelPagerAdapter(Claim channelClaim, FragmentActivity activity) { diff --git a/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java b/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java index e9b9f6b5..ab5417b7 100644 --- a/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/following/FollowingFragment.java @@ -2,6 +2,7 @@ package io.lbry.browser.ui.following; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.view.LayoutInflater; @@ -11,6 +12,8 @@ import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -45,8 +48,11 @@ import io.lbry.browser.utils.Helper; import io.lbry.browser.utils.Lbry; import io.lbry.browser.utils.LbryUri; import io.lbry.browser.utils.Lbryio; +import io.lbry.browser.utils.Predefined; -public class FollowingFragment extends BaseFragment implements FetchSubscriptionsTask.FetchSubscriptionsHandler, ChannelItemSelectionListener { +public class FollowingFragment extends BaseFragment implements + FetchSubscriptionsTask.FetchSubscriptionsHandler, + ChannelItemSelectionListener, SharedPreferences.OnSharedPreferenceChangeListener { private static final int SUGGESTED_PAGE_SIZE = 45; private static final int MIN_SUGGESTED_SUBSCRIBE_COUNT = 5; @@ -74,6 +80,7 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription private List contentSortOrder; private boolean contentClaimSearchLoading = false; private boolean suggestedClaimSearchLoading = false; + private View noContentView; private List queuedContentPages = new ArrayList<>(); private List queuedSuggestedPages = new ArrayList<>(); @@ -122,6 +129,7 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription contentLoading = root.findViewById(R.id.following_content_progress); channelListLoading = root.findViewById(R.id.following_channel_load_progress); discoverLink = root.findViewById(R.id.following_discover_link); + noContentView = root.findViewById(R.id.following_no_claim_search_content); Context context = getContext(); GridLayoutManager glm = new GridLayoutManager(context, 3); @@ -318,6 +326,7 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription public void onResume() { super.onResume(); + PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this); // check if subscriptions exist if (suggestedChannelAdapter != null) { @@ -338,6 +347,10 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription fetchSubscriptions(); } } + public void onPause() { + PreferenceManager.getDefaultSharedPreferences(getContext()).unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); + } public void loadFollowing() { // wrapper to just re-fetch subscriptions (upon user sign in, for example) @@ -350,10 +363,13 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription } private Map buildSuggestedOptions() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + return Lbry.buildClaimSearchOptions( Claim.TYPE_CHANNEL, null, - null, + canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS), null, excludeChannelIdsForDiscover, Arrays.asList(Claim.ORDER_BY_EFFECTIVE_AMOUNT), @@ -363,10 +379,13 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription } private Map buildContentOptions() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + return Lbry.buildClaimSearchOptions( Claim.TYPE_STREAM, null, - null, + canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS), getChannelIds(), null, getContentSortOrder(), @@ -514,6 +533,7 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription } contentClaimSearchLoading = true; + Helper.setViewVisibility(noContentView, View.GONE); Map claimSearchOptions = buildContentOptions(); contentClaimSearchTask = new ClaimSearchTask(claimSearchOptions, Lbry.LBRY_TV_CONNECTION_STRING, getLoadingView(), new ClaimSearchTask.ClaimSearchResultHandler() { @Override @@ -551,11 +571,13 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription contentHasReachedEnd = hasReachedEnd; contentClaimSearchLoading = false; + checkNoContent(false); } @Override public void onError(Exception error) { contentClaimSearchLoading = false; + checkNoContent(false); } }); contentClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -571,10 +593,16 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription } private void fetchSuggestedChannels() { + if (suggestedClaimSearchLoading) { + return; + } + suggestedClaimSearchLoading = true; if (discoverDialog != null) { discoverDialog.setLoading(true); } + + Helper.setViewVisibility(noContentView, View.GONE); suggestedChannelClaimSearchTask = new ClaimSearchTask( buildSuggestedOptions(), Lbry.LBRY_TV_CONNECTION_STRING, @@ -600,6 +628,10 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription } else { suggestedChannelAdapter.addClaims(claims); } + + if (discoverDialog == null || !discoverDialog.isVisible()) { + checkNoContent(true); + } } @Override @@ -608,6 +640,9 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription if (discoverDialog != null) { discoverDialog.setLoading(false); } + if (discoverDialog == null || !discoverDialog.isVisible()) { + checkNoContent(true); + } } }); @@ -645,13 +680,21 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription subscription.setUrl(claim.getPermanentUrl()); String channelClaimId = claim.getClaimId(); - ChannelSubscribeTask task = new ChannelSubscribeTask(getContext(), channelClaimId, subscription, false, null); + ChannelSubscribeTask task = new ChannelSubscribeTask(getContext(), channelClaimId, subscription, false, new ChannelSubscribeTask.ChannelSubscribeHandler() { + @Override + public void onSuccess() { + if (discoverDialog != null) { + fetchSubscriptions(); + } + } + + @Override + public void onError(Exception exception) { + + } + }); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); updateSuggestedDoneButtonText(); - - if (discoverDialog != null) { - fetchSubscriptions(); - } } public void onChannelItemDeselected(Claim claim) { // unsubscribe @@ -660,15 +703,35 @@ public class FollowingFragment extends BaseFragment implements FetchSubscription subscription.setUrl(claim.getPermanentUrl()); String channelClaimId = claim.getClaimId(); - ChannelSubscribeTask task = new ChannelSubscribeTask(getContext(), channelClaimId, subscription, true, null); + ChannelSubscribeTask task = new ChannelSubscribeTask(getContext(), channelClaimId, subscription, true, new ChannelSubscribeTask.ChannelSubscribeHandler() { + @Override + public void onSuccess() { + if (discoverDialog != null) { + fetchSubscriptions(); + } + } + + @Override + public void onError(Exception exception) { + + } + }); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); updateSuggestedDoneButtonText(); - - if (discoverDialog != null) { - fetchSubscriptions(); - } } public void onChannelSelectionCleared() { } + + private void checkNoContent(boolean suggested) { + RecyclerView.Adapter adpater = suggested ? suggestedChannelAdapter : contentListAdapter; + boolean noContent = adpater == null || adpater.getItemCount() == 0; + Helper.setViewVisibility(noContentView, noContent ? View.VISIBLE : View.GONE); + } + + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (key.equalsIgnoreCase(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT)) { + fetchClaimSearchContent(true); + } + } } 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 a344cf81..a68eb816 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 @@ -1,5 +1,6 @@ package io.lbry.browser.ui.search; +import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.view.LayoutInflater; @@ -10,6 +11,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -29,7 +31,8 @@ import io.lbry.browser.utils.Lbry; import io.lbry.browser.utils.LbryUri; import lombok.Setter; -public class SearchFragment extends BaseFragment implements ClaimListAdapter.ClaimListItemListener { +public class SearchFragment extends BaseFragment implements + ClaimListAdapter.ClaimListItemListener, SharedPreferences.OnSharedPreferenceChangeListener { private ClaimListAdapter resultListAdapter; private static final int PAGE_SIZE = 25; @@ -84,18 +87,20 @@ public class SearchFragment extends BaseFragment implements ClaimListAdapter.Cla public void onResume() { super.onResume(); - if (resultListAdapter == null || resultListAdapter.getItemCount() == 0) { - // new search - if (!Helper.isNullOrEmpty(currentQuery)) { - search(currentQuery, currentFrom); - } - } - if (Helper.isNullOrEmpty(currentQuery)) { + PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this); + if (!Helper.isNullOrEmpty(currentQuery)) { + search(currentQuery, currentFrom); + } else { noQueryView.setVisibility(View.VISIBLE); noResultsView.setVisibility(View.GONE); } } + public void onPause() { + PreferenceManager.getDefaultSharedPreferences(getContext()).unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); + } + private boolean checkQuery(String query) { if (!Helper.isNullOrEmpty(query) && !query.equalsIgnoreCase(currentQuery)) { // new query, reset values @@ -176,7 +181,10 @@ public class SearchFragment extends BaseFragment implements ClaimListAdapter.Cla } searchLoading = true; - LighthouseSearchTask task = new LighthouseSearchTask(currentQuery, PAGE_SIZE, currentFrom, false, null, loadingView, new ClaimSearchTask.ClaimSearchResultHandler() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + LighthouseSearchTask task = new LighthouseSearchTask( + currentQuery, PAGE_SIZE, currentFrom, canShowMatureContent, null, loadingView, new ClaimSearchTask.ClaimSearchResultHandler() { @Override public void onSuccess(List claims, boolean hasReachedEnd) { contentHasReachedEnd = hasReachedEnd; @@ -227,4 +235,10 @@ public class SearchFragment extends BaseFragment implements ClaimListAdapter.Cla MainActivity.openFileClaim(claim, getContext()); } } + + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (key.equalsIgnoreCase(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT)) { + search(currentQuery, currentFrom); + } + } } 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 6dd67d0b..d0064e01 100644 --- a/app/src/main/java/io/lbry/browser/utils/Helper.java +++ b/app/src/main/java/io/lbry/browser/utils/Helper.java @@ -32,14 +32,15 @@ import java.util.Random; 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 okhttp3.MediaType; public final class Helper { public static final String METHOD_GET = "GET"; public static final String METHOD_POST = "POST"; + public static final String ISO_DATE_FORMAT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS"; public static final MediaType FORM_MEDIA_TYPE = MediaType.parse("application/x-www-form-urlencoded"); public static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8"); - public static final List MATURE_TAG_NAMES = Arrays.asList("mature", "nsfw", "porn", "xxx"); public static final int CONTENT_PAGE_SIZE = 25; public static boolean isNull(String value) { @@ -283,4 +284,43 @@ public final class Helper { ((ColorDrawable) bg).setColor(isPlaceholder ? ContextCompat.getColor(context, android.R.color.transparent) : color); } } + + public static List getTagObjectsForTags(List tags) { + List tagObjects = new ArrayList<>(tags.size()); + for (String tag : tags) { + tagObjects.add(new Tag(tag)); + } + return tagObjects; + } + public static List getTagsForTagObjects(List tagObjects) { + List tags = new ArrayList<>(tagObjects.size()); + for (Tag tagObject : tagObjects) { + tags.add(tagObject.getLowercaseName()); + } + return tags; + } + public static List mergeKnownTags(List fetchedTags) { + List allKnownTags = getTagObjectsForTags(Predefined.DEFAULT_KNOWN_TAGS); + List followIndexes = new ArrayList<>(); + for (Tag tag : fetchedTags) { + if (!allKnownTags.contains(tag)) { + allKnownTags.add(tag); + } else if (tag.isFollowed()) { + followIndexes.add(allKnownTags.indexOf(tag)); + } + } + for (int index : followIndexes) { + allKnownTags.get(index).setFollowed(true); + } + return allKnownTags; + } + public static List filterFollowedTags(List tags) { + List followedTags = new ArrayList<>(); + for (Tag tag : followedTags) { + if (tag.isFollowed()) { + followedTags.add(tag); + } + } + return followedTags; + } } 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 31cbe984..645dd596 100644 --- a/app/src/main/java/io/lbry/browser/utils/Lbry.java +++ b/app/src/main/java/io/lbry/browser/utils/Lbry.java @@ -28,7 +28,9 @@ import io.lbry.browser.exceptions.LbryRequestException; import io.lbry.browser.exceptions.LbryResponseException; import io.lbry.browser.model.Claim; import io.lbry.browser.model.ClaimCacheKey; +import io.lbry.browser.model.ClaimSearchCacheValue; import io.lbry.browser.model.File; +import io.lbry.browser.model.Tag; import io.lbry.browser.model.Transaction; import io.lbry.browser.model.WalletBalance; import io.lbry.browser.model.lbryinc.User; @@ -39,13 +41,16 @@ import okhttp3.RequestBody; import okhttp3.Response; public final class Lbry { - public static final String TAG = "Lbry"; public static LinkedHashMap claimCache = new LinkedHashMap<>(); - public static LinkedHashMap, List> claimSearchCache = new LinkedHashMap<>(); + public static LinkedHashMap, ClaimSearchCacheValue> claimSearchCache = new LinkedHashMap<>(); public static WalletBalance walletBalance = new WalletBalance(); + public static List knownTags = new ArrayList<>(); + public static List followedTags = new ArrayList<>(); + public static final int TTL_CLAIM_SEARCH_VALUE = 120000; // 2-minute TTL for cache public static final String SDK_CONNECTION_STRING = "http://127.0.0.1:5279"; public static final String LBRY_TV_CONNECTION_STRING = "https://api.lbry.tv/api/v1/proxy"; + public static final String TAG = "Lbry"; // Values to obtain from LBRY SDK status public static boolean IS_STATUS_PARSED = false; // Check if the status has been parsed at least once @@ -360,7 +365,10 @@ public final class Lbry { public static List claimSearch(Map options, String connectionString) throws ApiCallException { if (claimSearchCache.containsKey(options)) { - return claimSearchCache.get(options); + ClaimSearchCacheValue value = claimSearchCache.get(options); + if (!value.isExpired(TTL_CLAIM_SEARCH_VALUE)) { + return claimSearchCache.get(options).getClaims(); + } } List claims = new ArrayList<>(); @@ -377,7 +385,7 @@ public final class Lbry { } } - claimSearchCache.put(options, claims); + claimSearchCache.put(options, new ClaimSearchCacheValue(claims, System.currentTimeMillis())); } catch (LbryRequestException | LbryResponseException | JSONException ex) { throw new ApiCallException("Could not execute resolve call", ex); } diff --git a/app/src/main/java/io/lbry/browser/utils/LbryUri.java b/app/src/main/java/io/lbry/browser/utils/LbryUri.java index 5fb901b8..aa7517e9 100644 --- a/app/src/main/java/io/lbry/browser/utils/LbryUri.java +++ b/app/src/main/java/io/lbry/browser/utils/LbryUri.java @@ -68,6 +68,10 @@ public class LbryUri { } } + if (components.size() == 0) { + throw new LbryUriException("Regular expression error occurred while trying to parse the value"); + } + // components[0] = proto // components[1] = streamNameOrChannelName // components[2] = primaryModSeparator @@ -141,7 +145,7 @@ public class LbryUri { return uri; } - public String build(boolean includeProto, String protoDefault) { + public String build(boolean includeProto, String protoDefault, boolean vanity) { String formattedChannelName = null; if (channelName != null) { formattedChannelName = channelName.startsWith("@") ? channelName : String.format("@%s", channelName); @@ -162,6 +166,15 @@ public class LbryUri { primaryClaimId = !Helper.isNullOrEmpty(formattedChannelName) ? channelClaimId : streamClaimId; } + StringBuilder sb = new StringBuilder(); + if (includeProto) { + sb.append(protoDefault); + } + sb.append(primaryClaimName); + if (vanity) { + return sb.toString(); + } + String secondaryClaimName = null; if (Helper.isNullOrEmpty(claimName) && !Helper.isNullOrEmpty(contentName)) { secondaryClaimName = contentName; @@ -171,11 +184,6 @@ public class LbryUri { } String secondaryClaimId = !Helper.isNullOrEmpty(secondaryClaimName) ? streamClaimId : null; - StringBuilder sb = new StringBuilder(); - if (includeProto) { - sb.append(protoDefault); - } - sb.append(primaryClaimName); if (!Helper.isNullOrEmpty(primaryClaimId)) { sb.append('#').append(primaryClaimId); } @@ -205,8 +213,11 @@ public class LbryUri { return parse(url).toString(); } + public String toVanityString() { + return build(true, PROTO_DEFAULT, true); + } public String toString() { - return build(true, PROTO_DEFAULT); + return build(true, PROTO_DEFAULT, false); } public int hashCode() { return toString().hashCode(); diff --git a/app/src/main/java/io/lbry/browser/utils/Predefined.java b/app/src/main/java/io/lbry/browser/utils/Predefined.java new file mode 100644 index 00000000..b6783523 --- /dev/null +++ b/app/src/main/java/io/lbry/browser/utils/Predefined.java @@ -0,0 +1,492 @@ +package io.lbry.browser.utils; + +import java.util.Arrays; +import java.util.List; + +public final class Predefined { + public static final List DEFAULT_KNOWN_TAGS = Arrays.asList( + "free speech", + "censorship", + "gaming", + "pop culture", + "entertainment", + "technology", + "music", + "funny", + "education", + "learning", + "news", + "gameplay", + "nature", + "beliefs", + "comedy", + "games", + "film & animation", + "whothinks", + "game", + "weapons", + "blockchain", + "video game", + "sports", + "walkthrough", + "art", + "pc", + "minecraft", + "playthrough", + "economics", + "automotive", + "play", + "tutorial", + "twitch", + "how to", + "ps4", + "bitcoin", + "fortnite", + "commentary", + "lets play", + "fun", + "politics", + "travel", + "food", + "science", + "xbox", + "liberal", + "democrat", + "progressive", + "survival", + "non-profits", + "activism", + "cryptocurrency", + "playstation", + "nintendo", + "government", + "steam", + "podcast", + "gamer", + "horror", + "conservative", + "reaction", + "trailer", + "love", + "cnn", + "republican", + "political", + "hangoutsonair", + "hoa", + "msnbc", + "cbs", + "anime", + "donald trump", + "fiction", + "fox news", + "crypto", + "ethereum", + "call of duty", + "android", + "multiplayer", + "epic", + "rpg", + "adventure", + "secular talk", + "btc", + "atheist", + "atheism", + "video games", + "ps3", + "cod", + "online", + "agnostic", + "movie", + "fps", + "lets", + "mod", + "world", + "reviews", + "sharefactory", + "space", + "pokemon", + "stream", + "hilarious", + "lol", + "sony", + "god", + "dance", + "pvp", + "tech", + "strategy", + "zombies", + "fail", + "film", + "xbox360", + "animation", + "unboxing", + "money", + "wwe", + "mods", + "indie", + "pubg", + "ios", + "history", + "rap", + "mobile", + "trump", + "hack", + "flat earth", + "trap", + "humor", + "vlogging", + "fox", + "news radio", + "facebook", + "edm", + "fitness", + "vaping", + "hip hop", + "secular", + "jesus", + "song", + "vape", + "guitar", + "remix", + "mining", + "daily", + "diy", + "pets", + "videogame", + "death", + "funny moments", + "religion", + "media", + "viral", + "war", + "nbc", + "freedom", + "gold", + "family", + "meme", + "zombie", + "photography", + "chill", + "sniper", + "computer", + "iphone", + "dragon", + "bible", + "pro", + "overwatch", + "litecoin", + "gta", + "house", + "fire", + "bass", + "truth", + "crash", + "mario", + "league of legends", + "wii", + "mmorpg", + "health", + "marvel", + "racing", + "apple", + "instrumental", + "earth", + "destiny", + "satire", + "race", + "training", + "electronic", + "boss", + "roblox", + "family friendly", + "california", + "react", + "christian", + "mmo", + "twitter", + "help", + "star", + "cars", + "random", + "top 10", + "ninja", + "guns", + "linux", + "lessons", + "vegan", + "future", + "dota 2", + "studio", + "star wars", + "shooting", + "nasa", + "rock", + "league", + "subscribe", + "water", + "gta v", + "car", + "samsung", + "music video", + "skyrim", + "dog", + "comics", + "shooter game", + "bo3", + "halloween", + "liberty", + "eth", + "conspiracy", + "knife", + "fashion", + "stories", + "vapor", + "nvidia", + "cute", + "beat", + "nintendo switch", + "fantasy", + "christmas", + "world of warcraft", + "industry", + "cartoon", + "garden", + "animals", + "windows", + "happy", + "magic", + "memes", + "design", + "tactical", + "fallout 4", + "puzzle", + "parody", + "rv", + "beats", + "building", + "disney", + "drone", + "ps2", + "beach", + "metal", + "christianity", + "business", + "mix", + "bo2", + "cover", + "senate", + "4k", + "united states", + "final", + "hero", + "playing", + "dlc", + "ubisoft", + "halo", + "pc gaming", + "raw", + "investing", + "online learning", + "software", + "ark", + "mojang", + "console", + "battle royale", + "canon", + "microsoft", + "camping", + "ufo", + "progressive talk", + "switch", + "fpv", + "arcade", + "school", + "driving", + "bodybuilding", + "drama", + "retro", + "science fiction", + "eggs", + "australia", + "modded", + "rainbow", + "gamers", + "resident evil", + "drawing", + "brasil", + "england", + "hillary clinton", + "singing", + "final fantasy", + "hiphop", + "video blog", + "mature", + "quad", + "noob", + "simulation", + "illuminati", + "poetry", + "dayz", + "manga", + "howto", + "insane", + "press", + "special", + "church", + "ico", + "weird", + "libertarian", + "crafting", + "level", + "comic", + "sandbox", + "daily vlog", + "outdoor", + "black ops", + "sound", + "christ", + "duty", + "juvenile fiction", + "pc game", + "how-to", + "ww2", + "creepy", + "artist", + "galaxy", + "destiny 2", + "new music", + "quest", + "lee", + "pacman", + "super smash bros", + "day", + "survival horror", + "patreon", + "bitcoin price", + "trending", + "open world", + "wii u", + "dope", + "reaper", + "sniping", + "dubstep", + "truck", + "planet", + "dc", + "amazon", + "spirituality", + "universe", + "video game culture", + "community", + "cat", + "aliens", + "tourism", + "altcoins", + "style", + "travel trailer", + "rda", + "gun", + "secret", + "far cry 5", + "auto", + "culture", + "dj", + "mw2", + "lord", + "full time rving", + "role-playing game", + "prank", + "grand theft auto", + "master", + "wrestling", + "sci-fi", + "workout", + "ghost", + "fake news", + "silly", + "season", + "bo4", + "trading", + "extreme", + "economy", + "combat", + "plays", + "muslim", + "pubg mobile", + "clips", + "bo1", + "paypal", + "sims", + "exploration", + "light", + "ripple", + "paranormal", + "football", + "capcom", + "rta", + "discord", + "batman", + "player", + "server", + "anarchy", + "military", + "playlist", + "cosplay", + "rv park", + "rant", + "edit", + "germany", + "reading", + "chris", + "flash", + "loot", + "bitcoin gratis", + "game reviews", + "movies", + "stupid", + "latest news", + "squad gameplay", + "guru", + "timelapse", + "black ops 3", + "holiday", + "soul", + "motivation", + "mw3", + "vacation", + "sega", + "19th century", + "pop", + "sims 4", + "post", + "smok", + "island", + "scotland", + "paladins", + "warrior", + "creepypasta", + "role-playing", + "solar", + "vr", + "animal", + "peace", + "consciousness", + "dota", + "audio", + "mass effect", + "humour", + "first look", + "videogames", + "future bass", + "freestyle", + "hardcore", + "portugal", + "dantdm", + "teaser", + "lbry", + "coronavirus", + "covidcuts", + "covid-19" + ); + public static final List MATURE_TAGS = Arrays.asList("mature", "nsfw", "porn", "xxx"); +} diff --git a/app/src/main/res/drawable-anydpi/ic_add.xml b/app/src/main/res/drawable-anydpi/ic_add.xml new file mode 100644 index 00000000..fa797053 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_add.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/ic_add.png b/app/src/main/res/drawable-hdpi/ic_add.png new file mode 100644 index 0000000000000000000000000000000000000000..2b125f5f3eaf0454a5d1b737a2a2bcb72884334a GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBqCH(4Ln>~)y>^h3L6L_w@V2M5 z=J7He6RnC5N*Wp}M$G@WwQoOhYMF~m$+VfCv->*P1KOt;va)apENGv8FRPO2Y47u= w*KXgRRmb|NG^HlBfq{{W>C@NA{w^0+)d*@l7C3eH8_*gCPgg&ebxsLQ041?C9{>OV literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_add.png b/app/src/main/res/drawable-mdpi/ic_add.png new file mode 100644 index 0000000000000000000000000000000000000000..a69d739e4a7056f8ff0217f04ebd0a87bbdf3d76 GIT binary patch literal 101 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjI-V|$Ar-fh6C_v{Gf4jV|G(al yqd&?Zi}54d#4h%J*2NQTvKZg$NU%B!Gce?a?`d4*H>V1ykHOQ`&t;ucLK6VsW*jE~ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_add.png b/app/src/main/res/drawable-xhdpi/ic_add.png new file mode 100644 index 0000000000000000000000000000000000000000..4423088d35681ee408159621049ba1fcc6adc0e0 GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCH%}MGkcwMxuNZPMDDbcx{PSPh z?n}WQz4pa#OAB+<0S5z}FY-g-dgorROcup$W!kPT&lY9a1 QuMHqUPgg&ebxsLQ03InLkpKVy literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_add.png b/app/src/main/res/drawable-xxhdpi/ic_add.png new file mode 100644 index 0000000000000000000000000000000000000000..94b04c8fbc9dadfe1baa02b73483f53dfe95bd98 GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!*`6+rAr-gYUfsycV8Frb_$|KK zaW1RJbS0 + android:textFontWeight="300" + android:visibility="gone" /> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_customize_tags.xml b/app/src/main/res/layout/dialog_customize_tags.xml new file mode 100644 index 00000000..b19a9ff9 --- /dev/null +++ b/app/src/main/res/layout/dialog_customize_tags.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_all_content.xml b/app/src/main/res/layout/fragment_all_content.xml index dc1100f8..b1e8ef79 100644 --- a/app/src/main/res/layout/fragment_all_content.xml +++ b/app/src/main/res/layout/fragment_all_content.xml @@ -180,6 +180,17 @@ android:visibility="gone" /> + + + + - + + diff --git a/app/src/main/res/layout/list_item_tag.xml b/app/src/main/res/layout/list_item_tag.xml index 63530d5b..5aa6709d 100644 --- a/app/src/main/res/layout/list_item_tag.xml +++ b/app/src/main/res/layout/list_item_tag.xml @@ -1,21 +1,29 @@ - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml deleted file mode 100644 index b700902f..00000000 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml deleted file mode 100644 index a2411e31..00000000 --- a/app/src/main/res/menu/main.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index d998e256..3eff1cb0 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -13,6 +13,7 @@ #0E0E0E #CC000000 + #EEEEEE #999999 #333333 #5F5F5F @@ -21,8 +22,8 @@ #40B887 #2F9176 #38D9A9 - #E3F6F1 - #77F255DA + #329A7E + #77D510B8 #454545 #0A0A0A diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 46392492..0a5ca6fc 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -13,6 +13,7 @@ #F1F1F1 #CC000000 + #222222 #333333 #333333 #777777 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 14f9a4cc..f15c3eed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,21 +2,11 @@ LBRY Open navigation drawer Close navigation drawer - Navigation header - Settings - - Home - Gallery - Slideshow - - Home Second - Find Content Your Content Wallet - Following Editor\'s Choice Your Tags @@ -29,9 +19,9 @@ Invites Settings About - Sign In App startup failed. Please check your data connection and try again. If this problem persists, please email hello@lbry.com + No content to display at this time. Please refine your selection or check back later. Welcome to LBRY. @@ -59,7 +49,6 @@ Tags - Share Repost Tip @@ -79,10 +68,11 @@ Website - User interface + Content & User interface Other Enable dark theme - Show URL suggestsions + Show mature content + Show URL suggestions Notifications Subscriptions Content Interests @@ -172,6 +162,7 @@ + Customize your tags Sort content by Content from Trending content @@ -187,9 +178,16 @@ All time from for + Filter for Everyone + Tags you follow Customize The selected view is not yet available. + It looks like you have not followed any tags yet. + Search for more tags + You have not followed any tags yet. Get started by adding tags that you are interested in! + We could not find new tags that you\'re not following. + The \'%1$s\' tag has already been added. Please provide an email address. diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 7ebffcff..a241c715 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -10,6 +10,10 @@ app:key="io.lbry.browser.preference.userinterface.DarkMode" app:title="@string/enable_dark_mode" app:iconSpaceReserved="false" /> +