From cfa7e07eba95400b4eaceced77bd17bcd3e6ff72 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Fri, 1 May 2020 13:49:56 +0100 Subject: [PATCH] Editor's Choice. Reposts. Some ontent page tweaks. --- .../java/io/lbry/browser/MainActivity.java | 23 +- .../browser/adapter/ClaimListAdapter.java | 25 ++- .../adapter/EditorsChoiceItemAdapter.java | 117 ++++++++++ .../java/io/lbry/browser/model/Claim.java | 28 ++- .../ui/allcontent/AllContentFragment.java | 2 +- .../ui/channel/ChannelContentFragment.java | 2 +- .../editorschoice/EditorsChoiceFragment.java | 148 +++++++++++++ .../ui/following/FollowingFragment.java | 3 + .../res/layout/fragment_editors_choice.xml | 15 +- app/src/main/res/layout/list_item_channel.xml | 159 ++++++++------ .../res/layout/list_item_editors_choice.xml | 23 +- .../list_item_featured_search_result.xml | 199 +++++++++++------- app/src/main/res/layout/list_item_stream.xml | 179 ++++++++++------ app/src/main/res/values-night/colors.xml | 2 + app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 2 + 16 files changed, 687 insertions(+), 241 deletions(-) create mode 100644 app/src/main/java/io/lbry/browser/adapter/EditorsChoiceItemAdapter.java diff --git a/app/src/main/java/io/lbry/browser/MainActivity.java b/app/src/main/java/io/lbry/browser/MainActivity.java index 716e40c2..50129f1f 100644 --- a/app/src/main/java/io/lbry/browser/MainActivity.java +++ b/app/src/main/java/io/lbry/browser/MainActivity.java @@ -99,6 +99,7 @@ import io.lbry.browser.tasks.wallet.SyncSetTask; import io.lbry.browser.tasks.wallet.WalletBalanceTask; import io.lbry.browser.ui.BaseFragment; import io.lbry.browser.ui.channel.ChannelFragment; +import io.lbry.browser.ui.editorschoice.EditorsChoiceFragment; import io.lbry.browser.ui.following.FollowingFragment; import io.lbry.browser.ui.search.SearchFragment; import io.lbry.browser.ui.settings.SettingsFragment; @@ -202,7 +203,11 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener private boolean pendingFollowingReload; private final List supportedMenuItemIds = Arrays.asList( - NavMenuItem.ID_ITEM_FOLLOWING, NavMenuItem.ID_ITEM_ALL_CONTENT, NavMenuItem.ID_ITEM_WALLET, NavMenuItem.ID_ITEM_SETTINGS + NavMenuItem.ID_ITEM_FOLLOWING, + NavMenuItem.ID_ITEM_EDITORS_CHOICE, + NavMenuItem.ID_ITEM_ALL_CONTENT, + NavMenuItem.ID_ITEM_WALLET, + NavMenuItem.ID_ITEM_SETTINGS ); @Override @@ -350,6 +355,9 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener case NavMenuItem.ID_ITEM_FOLLOWING: openFragment(FollowingFragment.class, true, NavMenuItem.ID_ITEM_FOLLOWING); break; + case NavMenuItem.ID_ITEM_EDITORS_CHOICE: + openFragment(EditorsChoiceFragment.class, true, NavMenuItem.ID_ITEM_EDITORS_CHOICE); + break; case NavMenuItem.ID_ITEM_ALL_CONTENT: openFragment(AllContentFragment.class, true, NavMenuItem.ID_ITEM_ALL_CONTENT); break; @@ -553,9 +561,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener findViewById(R.id.wunderbar_close).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - findViewById(R.id.wunderbar).clearFocus(); - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + clearWunderbarFocus(view); } }); findViewById(R.id.wunderbar).setOnFocusChangeListener(new View.OnFocusChangeListener() { @@ -612,8 +618,7 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener openAllContentFragmentWithTag(urlSuggestion.getText()); break; } - findViewById(R.id.wunderbar).clearFocus(); - //findViewById(R.id.url_suggestions_container).setVisibility(View.GONE); + clearWunderbarFocus(findViewById(R.id.wunderbar)); } }); @@ -623,6 +628,12 @@ public class MainActivity extends AppCompatActivity implements SdkStatusListener urlSuggestionList.setAdapter(urlSuggestionListAdapter); } + private void clearWunderbarFocus(View view) { + findViewById(R.id.wunderbar).clearFocus(); + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + private void launchSearch(String text) { Fragment currentFragment = getCurrentFragment(); if (currentFragment instanceof SearchFragment) { diff --git a/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java b/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java index ed87c566..095d80fc 100644 --- a/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java +++ b/app/src/main/java/io/lbry/browser/adapter/ClaimListAdapter.java @@ -87,6 +87,8 @@ public class ClaimListAdapter extends RecyclerView.Adapter { + private static final int VIEW_TYPE_HEADER = 1; + private static final int VIEW_TYPE_CONTENT = 2; + + private Context context; + private List items; + @Setter + private EditorsChoiceItemListener listener; + + public EditorsChoiceItemAdapter(List items, Context context) { + this.context = context; + this.items = new ArrayList<>(items); + } + + public void addFeaturedItem(EditorsChoiceItem item) { + items.add(0, item); + notifyDataSetChanged(); + } + + public void addItems(List items) { + for (EditorsChoiceItem item : items) { + if (!this.items.contains(item)) { + this.items.add(item); + } + } + notifyDataSetChanged(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + protected ImageView thumbnailView; + protected TextView descriptionView; + protected TextView headerView; + protected TextView titleView; + protected View cardView; + + public ViewHolder(View v) { + super(v); + + cardView = v.findViewById(R.id.editors_choice_content_card); + descriptionView = v.findViewById(R.id.editors_choice_content_description); + titleView = v.findViewById(R.id.editors_choice_content_title); + + thumbnailView = v.findViewById(R.id.editors_choice_content_thumbnail); + headerView = v.findViewById(R.id.editors_choice_header_title); + } + } + + @Override + public int getItemCount() { + return items != null ? items.size() : 0; + } + + @Override + public int getItemViewType(int position) { + return items.get(position).isHeader() ? VIEW_TYPE_HEADER : VIEW_TYPE_CONTENT; + } + + @Override + public EditorsChoiceItemAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(context).inflate(R.layout.list_item_editors_choice, parent, false); + return new EditorsChoiceItemAdapter.ViewHolder(v); + } + + @Override + public void onBindViewHolder(EditorsChoiceItemAdapter.ViewHolder vh, int position) { + int type = getItemViewType(position); + EditorsChoiceItem item = items.get(position); + + vh.headerView.setVisibility(type == VIEW_TYPE_HEADER ? View.VISIBLE : View.GONE); + vh.cardView.setVisibility(type == VIEW_TYPE_CONTENT ? View.VISIBLE : View.GONE); + + vh.headerView.setText(item.getTitle()); + vh.titleView.setText(item.getTitle()); + vh.descriptionView.setText(item.getDescription()); + if (!Helper.isNullOrEmpty(item.getThumbnailUrl())) { + Glide.with(context.getApplicationContext()). + load(item.getThumbnailUrl()). + centerCrop(). + placeholder(R.drawable.bg_thumbnail_placeholder). + into(vh.thumbnailView); + } + + vh.cardView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null) { + listener.onEditorsChoiceItemClicked(item); + } + } + }); + } + + public interface EditorsChoiceItemListener { + void onEditorsChoiceItemClicked(EditorsChoiceItem item); + } +} diff --git a/app/src/main/java/io/lbry/browser/model/Claim.java b/app/src/main/java/io/lbry/browser/model/Claim.java index 549a656d..f2c266be 100644 --- a/app/src/main/java/io/lbry/browser/model/Claim.java +++ b/app/src/main/java/io/lbry/browser/model/Claim.java @@ -32,6 +32,7 @@ public class Claim { public static final String TYPE_STREAM = "stream"; public static final String TYPE_CHANNEL = "channel"; + public static final String TYPE_REPOST = "repost"; public static final String STREAM_TYPE_AUDIO = "audio"; public static final String STREAM_TYPE_IMAGE = "image"; @@ -73,7 +74,8 @@ public class Claim { private String shortUrl; private String txid; private String type; // claim | update | support - private String valueType; // stream | channel + private String valueType; // stream | channel | repost + private Claim repostedClaim; private Claim signingChannel; private String repostChannelUrl; private boolean isChannelSignatureValid; @@ -158,24 +160,30 @@ public class Claim { } public static Claim fromJSONObject(JSONObject claimObject) { + Claim claim = null; String claimJson = claimObject.toString(); Type type = new TypeToken(){}.getType(); Type streamMetadataType = new TypeToken(){}.getType(); Type channelMetadataType = new TypeToken(){}.getType(); Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - Claim claim = gson.fromJson(claimJson, type); + claim = gson.fromJson(claimJson, type); - // Specific value parsing try { - JSONObject value = claimObject.getJSONObject("value"); String valueType = claim.getValueType(); - if (value != null) { - String valueJson = value.toString(); - if (TYPE_STREAM.equalsIgnoreCase(valueType)) { - claim.setValue(gson.fromJson(valueJson, streamMetadataType)); - } else if (TYPE_CHANNEL.equalsIgnoreCase(valueType)) { - claim.setValue(gson.fromJson(valueJson, channelMetadataType)); + // Specific value type parsing + if (TYPE_REPOST.equalsIgnoreCase(valueType)) { + JSONObject repostedClaimObject = claimObject.getJSONObject("reposted_claim"); + claim.setRepostedClaim(Claim.fromJSONObject(repostedClaimObject)); + } else { + JSONObject value = claimObject.getJSONObject("value"); + if (value != null) { + String valueJson = value.toString(); + if (TYPE_STREAM.equalsIgnoreCase(valueType)) { + claim.setValue(gson.fromJson(valueJson, streamMetadataType)); + } else if (TYPE_CHANNEL.equalsIgnoreCase(valueType)) { + claim.setValue(gson.fromJson(valueJson, channelMetadataType)); + } } } } catch (JSONException ex) { 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 7dfc45e4..d69194e5 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 @@ -376,7 +376,7 @@ public class AllContentFragment extends BaseFragment implements SharedPreference boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); return Lbry.buildClaimSearchOptions( - Claim.TYPE_STREAM, + null, (currentContentScope == ContentScopeDialogFragment.ITEM_EVERYONE) ? null : tags, canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS), null, 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 30cdb2a8..12f8da71 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 @@ -203,7 +203,7 @@ public class ChannelContentFragment extends Fragment implements SharedPreference 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), Arrays.asList(channelId), diff --git a/app/src/main/java/io/lbry/browser/ui/editorschoice/EditorsChoiceFragment.java b/app/src/main/java/io/lbry/browser/ui/editorschoice/EditorsChoiceFragment.java index 2644588f..914dea7e 100644 --- a/app/src/main/java/io/lbry/browser/ui/editorschoice/EditorsChoiceFragment.java +++ b/app/src/main/java/io/lbry/browser/ui/editorschoice/EditorsChoiceFragment.java @@ -1,6 +1,154 @@ package io.lbry.browser.ui.editorschoice; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; + +import androidx.annotation.NonNull; +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.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import io.lbry.browser.FileViewActivity; +import io.lbry.browser.MainActivity; +import io.lbry.browser.R; +import io.lbry.browser.adapter.ClaimListAdapter; +import io.lbry.browser.adapter.EditorsChoiceItemAdapter; +import io.lbry.browser.dialog.ContentScopeDialogFragment; +import io.lbry.browser.model.Claim; +import io.lbry.browser.model.EditorsChoiceItem; +import io.lbry.browser.tasks.ClaimSearchTask; import io.lbry.browser.ui.BaseFragment; +import io.lbry.browser.utils.Helper; +import io.lbry.browser.utils.Lbry; +import io.lbry.browser.utils.Predefined; public class EditorsChoiceFragment extends BaseFragment { + + private static final HashMap titleChannelIdsMap = new LinkedHashMap<>(); + static { + titleChannelIdsMap.put("Short Films", "7056f8267188fc49cd3f7162b4115d9e3c8216f6"); + titleChannelIdsMap.put("Feature-Length Films", "7aad6f36f61da95cb02471fae55f736b28e3bca7"); + titleChannelIdsMap.put("Documentaries", "d57c606e11462e821d5596430c336b58716193bb"); + titleChannelIdsMap.put("Episodic Content", "ea5fc1bd3e1335776fe2641a539a47850606d7db"); + } + + private boolean contentLoading; + private ProgressBar loading; + private RecyclerView contentList; + private EditorsChoiceItemAdapter contentListAdapter; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + View root = inflater.inflate(R.layout.fragment_editors_choice, container, false); + + loading = root.findViewById(R.id.editors_choice_loading); + contentList = root.findViewById(R.id.editors_choice_content_list); + + LinearLayoutManager llm = new LinearLayoutManager(getContext()); + contentList.setLayoutManager(llm); + + return root; + } + + private Map buildContentOptions() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); + boolean canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false); + return Lbry.buildClaimSearchOptions( + Claim.TYPE_REPOST, + null, + null, /*canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS),*/ + new ArrayList<>(titleChannelIdsMap.values()), + null, + Arrays.asList(Claim.ORDER_BY_RELEASE_TIME), + null, + 1, + 99); + } + + public void onResume() { + super.onResume(); + if (contentListAdapter == null || contentListAdapter.getItemCount() == 0) { + fetchClaimSearchContent(); + } else { + if (contentList != null) { + contentList.setAdapter(contentListAdapter); + } + } + } + + public void fetchClaimSearchContent() { + if (contentLoading) { + return; + } + + contentLoading = true; + ClaimSearchTask task = new ClaimSearchTask(buildContentOptions(), Lbry.LBRY_TV_CONNECTION_STRING, loading, new ClaimSearchTask.ClaimSearchResultHandler() { + @Override + public void onSuccess(List items, boolean hasReachedEnd) { + List data = buildDataFromClaims(items); + if (contentListAdapter == null) { + contentListAdapter = new EditorsChoiceItemAdapter(data, getContext()); + contentListAdapter.setListener(new EditorsChoiceItemAdapter.EditorsChoiceItemListener() { + @Override + public void onEditorsChoiceItemClicked(EditorsChoiceItem item) { + String url = item.getPermanentUrl(); + + Intent intent = new Intent(getContext(), FileViewActivity.class); + intent.putExtra("url", url); + MainActivity.startingFileViewActivity = true; + startActivity(intent); + } + }); + } else { + contentListAdapter.addItems(data); + } + + if (contentList != null && contentList.getAdapter() == null) { + contentList.setAdapter(contentListAdapter); + } + contentLoading = false; + } + + @Override + public void onError(Exception error) { + contentLoading = false; + } + }); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private List buildDataFromClaims(List claims) { + List data = new ArrayList<>(); + for (String title : titleChannelIdsMap.keySet()) { + EditorsChoiceItem titleItem = new EditorsChoiceItem(); + titleItem.setTitle(title); + titleItem.setHeader(true); + data.add(titleItem); + + String channelClaimId = titleChannelIdsMap.get(title); + for (Claim c : claims) { + if (c.getSigningChannel() != null && channelClaimId.equalsIgnoreCase(c.getSigningChannel().getClaimId())) { + EditorsChoiceItem item = EditorsChoiceItem.fromClaim( + Claim.TYPE_REPOST.equalsIgnoreCase(c.getValueType()) ? c.getRepostedClaim() : c); + data.add(item); + } + } + } + + return data; + } } 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 fd59d069..88971ca0 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 @@ -330,6 +330,9 @@ public class FollowingFragment extends BaseFragment implements // check if subscriptions exist if (suggestedChannelAdapter != null) { showSuggestedChannels(); + if (suggestedChannelGrid != null) { + suggestedChannelGrid.setAdapter(suggestedChannelAdapter); + } } if (Lbryio.subscriptions != null && Lbryio.subscriptions.size() > 0) { diff --git a/app/src/main/res/layout/fragment_editors_choice.xml b/app/src/main/res/layout/fragment_editors_choice.xml index 31bea4d1..42e03e9a 100644 --- a/app/src/main/res/layout/fragment_editors_choice.xml +++ b/app/src/main/res/layout/fragment_editors_choice.xml @@ -1,11 +1,20 @@ - + - \ No newline at end of file + android:layout_height="match_parent" + android:paddingTop="16dp" + android:paddingBottom="16dp" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_channel.xml b/app/src/main/res/layout/list_item_channel.xml index 3e9c4c5d..8ec69719 100644 --- a/app/src/main/res/layout/list_item_channel.xml +++ b/app/src/main/res/layout/list_item_channel.xml @@ -1,5 +1,5 @@ - + + + + + w - + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + + + + + + + + + - + + - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_editors_choice.xml b/app/src/main/res/layout/list_item_editors_choice.xml index 6963f987..bb75af89 100644 --- a/app/src/main/res/layout/list_item_editors_choice.xml +++ b/app/src/main/res/layout/list_item_editors_choice.xml @@ -2,26 +2,29 @@ + android:layout_marginBottom="16dp" + android:fontFamily="@font/inter" + android:textSize="24sp" + android:textColor="@color/lbryGreen" /> + android:layout_marginRight="16dp" + android:layout_marginBottom="16dp"> + android:textSize="18sp" + android:singleLine="true" /> + android:textSize="12sp" /> diff --git a/app/src/main/res/layout/list_item_featured_search_result.xml b/app/src/main/res/layout/list_item_featured_search_result.xml index 019c22f8..c40b520b 100644 --- a/app/src/main/res/layout/list_item_featured_search_result.xml +++ b/app/src/main/res/layout/list_item_featured_search_result.xml @@ -1,5 +1,5 @@ - + + + + + + android:layout_width="match_parent" + android:layout_height="wrap_content"> + android:id="@+id/claim_media_container" + android:layout_width="160dp" + android:layout_height="90dp"> + + + + + + + + + + + - - - + + + - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_stream.xml b/app/src/main/res/layout/list_item_stream.xml index 8d611504..1dbe8385 100644 --- a/app/src/main/res/layout/list_item_stream.xml +++ b/app/src/main/res/layout/list_item_stream.xml @@ -1,7 +1,8 @@ - - - - - - - - - + android:layout_marginBottom="4dp" + android:orientation="horizontal" + android:visibility="gone"> + - - - - + android:text="@string/reposted" + android:textColor="@color/lightForeground" + android:textFontWeight="300" + android:textSize="12sp" /> + + + + + + + + - \ No newline at end of file + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 3eff1cb0..b60f9626 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -13,7 +13,9 @@ #0E0E0E #CC000000 + #EEEEEE + #555555 #999999 #333333 #5F5F5F diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 0a5ca6fc..0411d94f 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -14,6 +14,7 @@ #CC000000 #222222 + #AAAAAA #333333 #333333 #777777 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f15c3eed..2b5f415f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -66,6 +66,7 @@ There\'s nothing here yet.\nPlease check back later. Content Website + reposted Content & User interface @@ -228,4 +229,5 @@ +